{"id":18555938,"url":"https://github.com/artemmey/ui-generator","last_synced_at":"2025-04-10T00:30:48.652Z","repository":{"id":42567492,"uuid":"269375010","full_name":"ArTemmey/ui-generator","owner":"ArTemmey","description":"Best UI development practices are now available for Android","archived":false,"fork":false,"pushed_at":"2022-03-31T16:29:24.000Z","size":557,"stargazers_count":28,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-24T13:51:16.305Z","etag":null,"topics":["android","android-annotation-processor","android-annotations","android-architecture","android-architecture-components","android-custom-view","android-databinding","android-fragments","android-framework","android-library","android-mvvm","android-ui","annotation","annotation-processor","architecture","architecture-components","code-generation","databinding","jetpack-compose","mvvm"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ArTemmey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-04T14:01:02.000Z","updated_at":"2025-03-21T14:16:15.000Z","dependencies_parsed_at":"2022-09-02T04:12:37.501Z","dependency_job_id":null,"html_url":"https://github.com/ArTemmey/ui-generator","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArTemmey%2Fui-generator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArTemmey%2Fui-generator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArTemmey%2Fui-generator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArTemmey%2Fui-generator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ArTemmey","download_url":"https://codeload.github.com/ArTemmey/ui-generator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248134908,"owners_count":21053563,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","android-annotation-processor","android-annotations","android-architecture","android-architecture-components","android-custom-view","android-databinding","android-fragments","android-framework","android-library","android-mvvm","android-ui","annotation","annotation-processor","architecture","architecture-components","code-generation","databinding","jetpack-compose","mvvm"],"created_at":"2024-11-06T21:28:24.867Z","updated_at":"2025-04-10T00:30:48.074Z","avatar_url":"https://github.com/ArTemmey.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"**UI-generator is a framework that allows you to intuitively and quickly create UI** using the principle of reusable components. This principle is the most modern and effective in the field of UI development, and it underlies such frameworks as React and Flutter.\n\n---\n**UI-generator is similar in functionality to [Jetpack Compose](https://developer.android.com/jetpack/compose)** and provides all its main features. But unlike the Jetpack Compose, UI-generator is fully available now and is compatible with the components of the Android support library - Fragments and Views, so you do not have to rewrite all your code to implement this framework. UI-generator works on annotation processing and generates code on top of Fragment and View classes.\n\n## Installation\n\nIn your root build.gradle.kts:\n```kts\nallprojects {\n   repositories {\n      ...\n      maven(url = \"https://jitpack.io\")\n   }\n}\n```\nIn your app/build.gradle.kts:\n```kts\nplugins {\n   id(\"com.google.devtools.ksp\") version \"\u003cksp-version\u003e\"\n}\n\nksp {\n    arg(\"packageName\", \"\u003cyour-package-name\u003e\")\n}\n\nandroid {\n   ...\n   dataBinding {\n      isEnabled = true\n   }\n}\n\ndependencies {\n   implementation(\"com.github.ArtemiyDmtrvch.ui-generator:ui-generator-base:+\")\n   implementation(\"com.github.ArtemiyDmtrvch.ui-generator:ui-generator-annotations:+\")\n   ksp(\"com.github.ArtemiyDmtrvch.ui-generator:ui-generator-processor:+\")\n}\n```\n## Why do you need UI-generator\n- You will write ***at least 2 times less code*** than if you wrote using Android SDK and any architecture.\n- The entry threshold into your project will be minimal, because there are very few rules, and they are simple and universal for all situations\n- Your code will be a priori reusable, and you will never have a situation when you have a Fragment, but you need to display it in the RecyclerView\n- The principles laid down in UI-generator are the most promising for development for any platform, and soon they will become the standard for Android development\n\n## Now let's see how this is all achieved\n\n### 1. One rule for all components\n\nSuppose you have a Fragment in which an argument is passed, which is then displayed in the TextView. Here's how you do it:\n```kotlin\n@MakeComponent\nclass MyFragment : ComponentScheme\u003cFragment, MyFragmentViewModel\u003e({ R.layout.my_fragment })\n\nclass MyFragmentViewModel : ComponentViewModel() {\n\n    @Prop\n    var myText by state\u003cString?\u003e(null)\n}\n```\n```xml\n// my_fragment.xml\n\u003clayout\u003e\n    \u003cdata\u003e\n        \u003cvariable\n            name=\"viewModel\"\n            type=\"my.package.MyFragmentViewModel\" /\u003e\n    \u003c/data\u003e\n\u003cTextView\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@{viewModel.myText}\" /\u003e\n\u003c/layout\u003e\n```\nThe annotation processor will generate a class **MyFragmentComponent** inherited from the Fragment with the ViewModel and the field `myText`. When this field is changed, an argument will be added to the Fragment, which will then be passed to the ViewModel and bound to the TextView using [Android data binding](https://developer.android.com/topic/libraries/data-binding). As a result, **this class can be used like this:**\n```kotlin\nshowFragment(MyFragmentComponent().apply { myText = \"Hello world!\" })\n```\n\nNow let's imagine a similar example, but you will have a FrameLayout to which the text is bound, which is then displayed in the TextView. And here is how you do it:\n```kotlin\n@MakeComponent\nclass MyLayout : ComponentScheme\u003cFrameLayout, MyLayoutViewModel\u003e({ R.layout.my_layout })\n\nclass MyLayoutViewModel : ComponentViewModel() {\n\n    @Prop\n    var myText by state\u003cString?\u003e(null)\n}\n```\n```xml\n// my_layout.xml\n\u003clayout\u003e\n    \u003cdata\u003e\n        \u003cvariable\n            name=\"viewModel\"\n            type=\"my.package.MyLayoutViewModel\" /\u003e\n    \u003c/data\u003e\n\u003cTextView\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@{viewModel.myText}\" /\u003e\n\u003c/layout\u003e\n```\nThe annotation processor will generate a class **MyLayoutComponent** inherited from the FrameLayout with the ViewModel and the [binding adapter](https://developer.android.com/topic/libraries/data-binding/binding-adapters) for `myText` attribute, which will pass the value to the ViewModel. As a result, **this class can be used like this:**\n```xml\n \u003cmy.package.MyLayoutComponent\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    myText='@{\"Hello world!\"}' /\u003e\n```\n\nAs you can see, the codes for the Fragment and for the View are completely identical. You do not need to write View and Fragment classes at all, they are generated automatically.\n\n**The single rule is:** create a class inherited from `ComponentScheme`, specify the super component as the first type argument, ViewModel as the second and mark this class with `MakeComponent` annotation. Then mark with `Prop` annotation those properties that may come from the parent component (the properties must be vars). An argument will be generated for the Fragment, and a binding adapter for the View. Then build the project and use generated classes.\n\n**Note:** you may need to build the project twice so that the binding adapters and component classes are generated correctly.\n\nAlso, in the case of a View:\n- You can set `Prop.twoWay = true`, and then a two-way binding adapter will be generated for the View. It will send the value back when the annotated property changes.\n```kotlin\n@Prop(twoWay = true)\nvar twoWayText: String? = null //a two-way binding adapter will be generated\n```\n- You can bind xml attribute to your state property:\n```kotlin\nvar picture by state\u003cDrawable?\u003e(null, attr = R.styleable.MyViewComponent_picture)\n```\n```xml\n\u003cMyViewComponent\n\tapp:picture=\"@drawable/myPicture\"/\u003e\n```\n\n### 2. Observable state\n\nFirst you create the `viewModel` variable in your layout.xml. Then you declare certain properties in the ViewModel by the `state` delegate. Each time one of these properties changes, data binding is performed. And this mechanism allows you to **forget about LiveData and ObservableFields.** Now the data for binding can be just vars.\n\nThis mechanism optimally distributes the load on the main thread (data binding is placed at the end of the message queue of the main Looper). And if there are many consecutive state changes data binding will only be done once:\n```kotlin\nproperty1 = \"Hello world!\"\nproperty2 = 123\nproperty3 = true\n//data binding will only be done once\n```\n\nData binding is performed at one time for all Views by replacing the old bound ViewModel with a new one. And this does not make the binding algorithm more complicated than using LiveData and ObservableFields, since all native data binding adapters and generated ones are not executed if the new value is the same as the old one.\n\nYou can manually initiate data binding by calling `onStateChanged` function in ViewModel.\n\n**Note:** two-way data binding also works - changes in the view will change your state property\n\n### 3. Functional rendering\n\nSuppose you need to display one or another layout, depending on the condition. Here's how you do it:\n```kotlin\n@MakeComponent\nclass MyScrollView : ComponentScheme\u003cScrollView, MyScrollViewModel\u003e({ viewModel -\u003e\n    if(viewModel.showFirstLayout)\n        R.layout.first_layout\n    else\n        R.layout.second_layout\n})\n```\nThis lambda is called at the same time as data binding, that is, after a state change. In essence, this is also data binding, but to a super component. In addition to the ViewModel, a super component is passed to this lambda as `this`, and you can bind any data to it:\n```kotlin\n@MakeComponent\nclass MyButton : ComponentScheme\u003cButton, MyButtonViewModel\u003e({ viewModel -\u003e\n    //Button is passed to this lambda as `this`\n    this.isEnabled = viewModel.isEnabled\n    null\n})\n```\n\nTo bind data to a super component, you can use the functions located in `ru.impression.ui_generator_base.Binders.kt`. All of them are executed only if the new value is different from the set value. Example of the function `updateLayoutParams`:\n```kotlin\n@MakeComponent\nclass MyTextView : ComponentScheme\u003cTextView, MyTextViewModel\u003e({\n    updateLayoutParams(width = MATCH_PARENT, height = WRAP_CONTENT, marginTop = 16)\n    null\n})\n```\n\n### 5. Coroutine support\n\n#### suspend funs\n\nSuppose that before you display some data, you need to load it first. Here's how you do it:\n```kotlin\nvar greeting: String? by state({\n    delay(2000)\n    \"Hello world!\"\n})\n```\nAll you need to do is inherit your model from `CoroutineViewModel`. It implements `CoroutineScope` in which your suspend lambda is executed. You can also execute all your other coroutines in this scope. Scope is canceled when `onCleared` is called.\n\nYou can also observe the loading state of your data. For example, in order to show the progress bar during loading:\n```xml\n\u003cProgressBar\n    isVisible=\"@{viewModel.greetingIsLoading}\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\" /\u003e\n```\n```kotlin\n// After `isLoading` becomes `false`, the data binding will be called and the ProgressBar will be hidden.\nval greetingIsLoading: Boolean get() = ::greeting.isLoading\n```\n\nAnd also you can reload your data:\n```kotlin\nfun reloadGreeting() {\n    // The suspend lambda will be called again and `isLoading` will become `true`.\n    // After that, the data binding will be called and the ProgressBar wil be shown again at loading time.\n    ::greeting.reload()\n}\n```\n\n#### Flows\n\nSuppose you need to subscribe to the Flow and display all its elements. Here's how you do it:\n```kotlin\nvar countDown: Int? by state(flow {\n    delay(1000)\n        emit(3)\n        delay(1000)\n        emit(2)\n        delay(1000)\n        emit(1)\n        delay(1000)\n        emit(0)\n})\n```\n\n***For detailed examples see module `app`.***","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartemmey%2Fui-generator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fartemmey%2Fui-generator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartemmey%2Fui-generator/lists"}