├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── composeform │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── composeform │ │ │ ├── BackEndService.kt │ │ │ ├── MainActivity.kt │ │ │ ├── ServiceLocator.kt │ │ │ └── ui │ │ │ ├── CheckBoxElement.kt │ │ │ ├── Color.kt │ │ │ ├── Form.kt │ │ │ ├── Screen.kt │ │ │ ├── Shape.kt │ │ │ ├── TextElement.kt │ │ │ ├── TextFieldElement.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── example │ └── composeform │ └── ExampleUnitTest.kt ├── build.gradle ├── data ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── data │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── example │ │ └── data │ │ └── ScreenDto.kt │ └── test │ └── java │ └── com │ └── example │ └── data │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | ### CUSTOM START - DO NOT REMOVE WHEN UPDATING ### 2 | .idea/jarRepositories.xml 3 | .idea/intellij-javadocs-*.xml 4 | ### CUSTOM END - DO NOT REMOVE WHEN UPDATING ### 5 | # Created by https://www.gitignore.io/api/git,gradle,android,intellij,androidstudio 6 | # Edit at https://www.gitignore.io/?templates=git,gradle,android,intellij,androidstudio 7 | 8 | ### Android ### 9 | # Built application files 10 | *.apk 11 | *.ap_ 12 | *.aab 13 | 14 | # Files for the ART/Dalvik VM 15 | *.dex 16 | 17 | # Java class files 18 | *.class 19 | 20 | # Generated files 21 | bin/ 22 | gen/ 23 | out/ 24 | release/ 25 | 26 | # Gradle files 27 | .gradle/ 28 | build/ 29 | 30 | # Local configuration file (sdk path, etc) 31 | local.properties 32 | 33 | # Proguard folder generated by Eclipse 34 | proguard/ 35 | 36 | # Log Files 37 | *.log 38 | 39 | # Android Studio Navigation editor temp files 40 | .navigation/ 41 | 42 | # Android Studio captures folder 43 | captures/ 44 | 45 | # IntelliJ 46 | *.iml 47 | .idea/workspace.xml 48 | .idea/tasks.xml 49 | .idea/gradle.xml 50 | .idea/assetWizardSettings.xml 51 | .idea/dictionaries 52 | .idea/libraries 53 | # Android Studio 3 in .gitignore file. 54 | .idea/caches 55 | .idea/modules.xml 56 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 57 | .idea/navEditor.xml 58 | 59 | # Keystore files 60 | # Uncomment the following lines if you do not want to check your keystore files in. 61 | #*.jks 62 | #*.keystore 63 | 64 | # External native build folder generated in Android Studio 2.2 and later 65 | .externalNativeBuild 66 | 67 | # Google Services (e.g. APIs or Firebase) 68 | # google-services.json 69 | 70 | # Freeline 71 | freeline.py 72 | freeline/ 73 | freeline_project_description.json 74 | 75 | # fastlane 76 | fastlane/report.xml 77 | fastlane/Preview.html 78 | fastlane/screenshots 79 | fastlane/test_output 80 | fastlane/readme.md 81 | 82 | # Version control 83 | vcs.xml 84 | 85 | # lint 86 | lint/intermediates/ 87 | lint/generated/ 88 | lint/outputs/ 89 | lint/tmp/ 90 | # lint/reports/ 91 | 92 | ### Android Patch ### 93 | gen-external-apklibs 94 | output.json 95 | 96 | # Replacement of .externalNativeBuild directories introduced 97 | # with Android Studio 3.5. 98 | .cxx/ 99 | 100 | ### Git ### 101 | # Created by git for backups. To disable backups in Git: 102 | # $ git config --global mergetool.keepBackup false 103 | *.orig 104 | 105 | # Created by git when using merge tools for conflicts 106 | *.BACKUP.* 107 | *.BASE.* 108 | *.LOCAL.* 109 | *.REMOTE.* 110 | *_BACKUP_*.txt 111 | *_BASE_*.txt 112 | *_LOCAL_*.txt 113 | *_REMOTE_*.txt 114 | 115 | ### Intellij ### 116 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 117 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 118 | 119 | # User-specific stuff 120 | .idea/**/workspace.xml 121 | .idea/**/tasks.xml 122 | .idea/**/usage.statistics.xml 123 | .idea/**/dictionaries 124 | .idea/**/shelf 125 | 126 | # Generated files 127 | .idea/**/contentModel.xml 128 | 129 | # Sensitive or high-churn files 130 | .idea/**/dataSources/ 131 | .idea/**/dataSources.ids 132 | .idea/**/dataSources.local.xml 133 | .idea/**/sqlDataSources.xml 134 | .idea/**/dynamic.xml 135 | .idea/**/uiDesigner.xml 136 | .idea/**/dbnavigator.xml 137 | 138 | # Gradle 139 | .idea/**/gradle.xml 140 | .idea/**/libraries 141 | 142 | # Gradle and Maven with auto-import 143 | # When using Gradle or Maven with auto-import, you should exclude module files, 144 | # since they will be recreated, and may cause churn. Uncomment if using 145 | # auto-import. 146 | # .idea/modules.xml 147 | # .idea/*.iml 148 | # .idea/modules 149 | # *.iml 150 | # *.ipr 151 | 152 | # CMake 153 | cmake-build-*/ 154 | 155 | # Mongo Explorer plugin 156 | .idea/**/mongoSettings.xml 157 | 158 | # File-based project format 159 | *.iws 160 | 161 | # IntelliJ 162 | 163 | # mpeltonen/sbt-idea plugin 164 | .idea_modules/ 165 | 166 | # JIRA plugin 167 | atlassian-ide-plugin.xml 168 | 169 | # Cursive Clojure plugin 170 | .idea/replstate.xml 171 | 172 | # Crashlytics plugin (for Android Studio and IntelliJ) 173 | com_crashlytics_export_strings.xml 174 | crashlytics.properties 175 | crashlytics-build.properties 176 | fabric.properties 177 | 178 | # Editor-based Rest Client 179 | .idea/httpRequests 180 | 181 | # Android studio 3.1+ serialized cache file 182 | .idea/caches/build_file_checksums.ser 183 | 184 | ### Intellij Patch ### 185 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 186 | 187 | # *.iml 188 | # modules.xml 189 | # .idea/misc.xml 190 | # *.ipr 191 | 192 | # Sonarlint plugin 193 | .idea/**/sonarlint/ 194 | 195 | # SonarQube Plugin 196 | .idea/**/sonarIssues.xml 197 | 198 | # Markdown Navigator plugin 199 | .idea/**/markdown-navigator.xml 200 | .idea/**/markdown-navigator/ 201 | 202 | ### Gradle ### 203 | .gradle 204 | 205 | # Ignore Gradle GUI config 206 | gradle-app.setting 207 | 208 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 209 | !gradle-wrapper.jar 210 | 211 | # Cache of project 212 | .gradletasknamecache 213 | 214 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 215 | # gradle/wrapper/gradle-wrapper.properties 216 | 217 | ### Gradle Patch ### 218 | **/build/ 219 | 220 | ### AndroidStudio ### 221 | # Covers files to be ignored for android development using Android Studio. 222 | 223 | # Built application files 224 | 225 | # Files for the ART/Dalvik VM 226 | 227 | # Java class files 228 | 229 | # Generated files 230 | 231 | # Gradle files 232 | 233 | # Signing files 234 | .signing/ 235 | 236 | # Local configuration file (sdk path, etc) 237 | 238 | # Proguard folder generated by Eclipse 239 | 240 | # Log Files 241 | 242 | # Android Studio 243 | /*/build/ 244 | /*/local.properties 245 | /*/out 246 | /*/*/build 247 | /*/*/production 248 | *.ipr 249 | *~ 250 | *.swp 251 | 252 | # Android Patch 253 | 254 | # External native build folder generated in Android Studio 2.2 and later 255 | 256 | # NDK 257 | obj/ 258 | 259 | # IntelliJ IDEA 260 | /out/ 261 | 262 | # User-specific configurations 263 | .idea/caches/ 264 | .idea/libraries/ 265 | .idea/shelf/ 266 | .idea/.name 267 | .idea/compiler.xml 268 | .idea/copyright/profiles_settings.xml 269 | .idea/encodings.xml 270 | .idea/misc.xml 271 | .idea/scopes/scope_settings.xml 272 | .idea/vcs.xml 273 | .idea/jsLibraryMappings.xml 274 | .idea/datasources.xml 275 | .idea/dataSources.ids 276 | .idea/sqlDataSources.xml 277 | .idea/dynamic.xml 278 | .idea/uiDesigner.xml 279 | .yo-rc.json 280 | 281 | # OS-specific files 282 | .DS_Store 283 | .DS_Store? 284 | ._* 285 | .Spotlight-V100 286 | .Trashes 287 | ehthumbs.db 288 | Thumbs.db 289 | 290 | # Legacy Eclipse project files 291 | .classpath 292 | .project 293 | .cproject 294 | .settings/ 295 | 296 | # Mobile Tools for Java (J2ME) 297 | .mtj.tmp/ 298 | 299 | # Package Files # 300 | *.war 301 | *.ear 302 | 303 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 304 | hs_err_pid* 305 | 306 | ## Plugin-specific files: 307 | 308 | # mpeltonen/sbt-idea plugin 309 | 310 | # JIRA plugin 311 | 312 | # Mongo Explorer plugin 313 | .idea/mongoSettings.xml 314 | 315 | # Crashlytics plugin (for Android Studio and IntelliJ) 316 | 317 | ### AndroidStudio Patch ### 318 | 319 | !/gradle/wrapper/gradle-wrapper.jar 320 | 321 | # End of https://www.gitignore.io/api/git,gradle,android,intellij,androidstudio 322 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | My Application -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 41 | 42 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 94 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API-Driven UI with Jetpack Compose 2 | 3 | This project demonstrates one technique for directly driving UI from API results using Jetpack Compose. 4 | 5 | Requires Android Studio Canary 4.2 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | } 6 | 7 | android { 8 | compileSdkVersion 30 9 | buildToolsVersion "30.0.2" 10 | 11 | defaultConfig { 12 | applicationId "com.example.composeform" 13 | minSdkVersion 23 14 | targetSdkVersion 30 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | useIR = true 34 | } 35 | buildFeatures { 36 | compose true 37 | } 38 | composeOptions { 39 | kotlinCompilerExtensionVersion compose_version 40 | kotlinCompilerVersion '1.4.10' 41 | } 42 | } 43 | 44 | dependencies { 45 | 46 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 47 | implementation 'androidx.core:core-ktx:1.3.2' 48 | implementation 'androidx.appcompat:appcompat:1.2.0' 49 | implementation 'com.google.android.material:material:1.2.1' 50 | implementation "androidx.compose.ui:ui:$compose_version" 51 | implementation "androidx.compose.material:material:$compose_version" 52 | implementation "androidx.ui:ui-tooling:$compose_version" 53 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha06' 54 | implementation "com.squareup.moshi:moshi:1.11.0" 55 | kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.11.0' 56 | implementation project(path: ':data') 57 | testImplementation 'junit:junit:4.+' 58 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 59 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 60 | } 61 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/composeform/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.example.composeform", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/BackEndService.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform 2 | 3 | 4 | interface BackEndService { 5 | fun getPage(path: String, parameters: Map): String 6 | } 7 | 8 | class FakeBackEndService : BackEndService { 9 | var fullname: String? = null 10 | override fun getPage(path: String, parameters: Map): String { 11 | return when(path) { 12 | "/" -> initialScreen 13 | "/check" -> secondScreen.also {fullname = parameters.get("first_name")+" "+parameters.get("last_name")} 14 | "/welcome" -> finalScreen 15 | .replace("##1##", fullname?:"") 16 | .replace("##2##", parameters.get("first_check")?:"") 17 | .replace("##3##", parameters.get("second_check")?:"") 18 | else -> initialScreen 19 | } 20 | } 21 | } 22 | 23 | val initialScreen = """ 24 | { 25 | "children" : [ 26 | { 27 | "viewtype" : "TEXT", 28 | "label" : "Form Header" 29 | }, 30 | { 31 | "viewtype" : "FORM", 32 | "children" : [ 33 | { 34 | "viewtype" : "TEXT", 35 | "label" : "Personal Information" 36 | }, 37 | { 38 | "viewtype" : "TEXTFIELD", 39 | "label" : "First", 40 | "data" : "first_name" 41 | }, { 42 | "viewtype" : "TEXTFIELD", 43 | "label": "Last", 44 | "data" : "last_name" 45 | } 46 | ], 47 | "label" : "Submit", 48 | "data" : "/check" 49 | } 50 | ] 51 | } 52 | """ 53 | 54 | val secondScreen = """ 55 | { 56 | "children" : [ 57 | { 58 | "viewtype" : "TEXT", 59 | "label" : "Form Header" 60 | }, 61 | { 62 | "viewtype" : "FORM", 63 | "children" : [ 64 | { 65 | "viewtype" : "TEXT", 66 | "label" : "Checkboxes" 67 | }, 68 | { 69 | "viewtype" : "CHECKBOX", 70 | "label" : "First", 71 | "data" : "first_check" 72 | }, { 73 | "viewtype" : "CHECKBOX", 74 | "label": "Last", 75 | "data" : "last_check" 76 | } 77 | ], 78 | "label" : "Submit", 79 | "data" : "/welcome" 80 | } 81 | ] 82 | } 83 | """ 84 | 85 | val finalScreen = """ 86 | { 87 | "children" : [ 88 | { 89 | "viewtype" : "TEXT", 90 | "label" : "Finished" 91 | }, 92 | { 93 | "viewtype" : "TEXT", 94 | "label" : "Full name: ##1##" 95 | }, 96 | { 97 | "viewtype" : "TEXT", 98 | "label" : "First Checkbox: ##2##" 99 | }, { 100 | "viewtype" : "TEXT", 101 | "label": "Last Checkbox: ##3##" 102 | } 103 | ] 104 | } 105 | """ 106 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform 2 | 3 | import android.os.Bundle 4 | import android.widget.Toast 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.compose.runtime.* 7 | import androidx.compose.ui.platform.setContent 8 | import androidx.ui.tooling.preview.Preview 9 | import com.example.composeform.ui.MyApplicationTheme 10 | import com.example.composeform.ui.Screen 11 | import com.example.data.ElementDto 12 | import com.example.data.ScreenDto 13 | import com.example.data.ViewType 14 | import com.squareup.moshi.Moshi 15 | 16 | class MainActivity : AppCompatActivity() { 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | ServiceLocator.put(BackEndService::class.java, FakeBackEndService()) 20 | setContent { 21 | MyApplicationTheme { 22 | MyScreenContent() 23 | } 24 | } 25 | } 26 | } 27 | 28 | data class StringHolder(var held: MutableState) 29 | val ScreenJson = ambientOf() 30 | 31 | @Composable 32 | fun MyScreenContent() { 33 | val screenJson = ServiceLocator.resolve(BackEndService::class.java).getPage("/", mapOf()) 34 | val screenJsonString = StringHolder(remember {mutableStateOf(screenJson)}) 35 | val moshi = Moshi.Builder() 36 | .build() 37 | val screenAdapter = moshi.adapter(ScreenDto::class.java) 38 | Providers(ScreenJson provides screenJsonString) { 39 | val holder = ScreenJson.current 40 | screenAdapter.fromJson(holder.held.value)?.let { 41 | Screen(it).compose() 42 | } 43 | } 44 | } 45 | 46 | @Preview(showBackground = true) 47 | @Composable 48 | fun DefaultPreview() { 49 | MyApplicationTheme { 50 | MyScreenContent() 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ServiceLocator.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform 2 | 3 | 4 | class ServiceLocator private constructor() { 5 | companion object { 6 | private val locatorMap: MutableMap, Any> = mutableMapOf() 7 | 8 | fun resolve(type: Class): T { 9 | return type.cast(locatorMap[type])!! 10 | } 11 | 12 | fun put(type: Class?, instance: T) { 13 | if (type == null) { 14 | throw NullPointerException("Type is null") 15 | } 16 | if (instance == null) { 17 | throw NullPointerException("Instance is null") 18 | } 19 | locatorMap.put(type, instance) 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ui/CheckBoxElement.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform.ui 2 | 3 | import androidx.compose.foundation.Text 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.material.Checkbox 6 | import androidx.compose.material.TextField 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.MutableState 9 | import androidx.compose.runtime.mutableStateOf 10 | import com.example.data.ElementDto 11 | 12 | class CheckBoxElement(val elementDto: ElementDto) : ComposableElement { 13 | val fieldName = elementDto.data?:"value" 14 | @Composable 15 | override fun compose(hoist: Map>) { 16 | Row { 17 | Checkbox(checked = hoist.get(fieldName)?.value.equals("true", true), onCheckedChange = {it -> hoist.get(fieldName)?.value = it.toString()}) 18 | Text (elementDto.label?:"") 19 | } 20 | } 21 | 22 | override fun getHoist(): Map> { 23 | return mapOf(Pair(fieldName, mutableStateOf(elementDto.default?:""))) 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ui/Color.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform.ui 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val red200 = Color(0xFFBB8600) 6 | val red500 = Color(0xFF620000) 7 | val red700 = Color(0xFF370000) 8 | val grey200 = Color(0xFFDADADA) -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ui/Form.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform.ui 2 | 3 | import androidx.compose.foundation.Text 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.material.Button 6 | import androidx.compose.runtime.* 7 | import com.example.composeform.BackEndService 8 | import com.example.composeform.ScreenJson 9 | import com.example.composeform.ServiceLocator 10 | import com.example.data.ElementDto 11 | 12 | class FormElement ( 13 | val elementDto: ElementDto 14 | ) : ComposableElement { 15 | @Composable 16 | override fun compose(hoist: Map>) { 17 | val childElements = elementDto.children?.map { it.getComposableElement() } ?: listOf() 18 | val children = childElements.map { Pair(it, it.getHoist() ) } 19 | Column { 20 | children.map { 21 | it.first.compose(it.second) 22 | } 23 | val json = ScreenJson.current 24 | Button(onClick = { 25 | val parameters = children.flatMap { it.second.entries.map { Pair(it.key, it.value.value) } }.toMap() 26 | val newPage = ServiceLocator.resolve(BackEndService::class.java).getPage(elementDto.data?:"", parameters) 27 | json.held.value = newPage 28 | }){ 29 | Text(elementDto.label?:"") 30 | } 31 | } 32 | } 33 | 34 | override fun getHoist(): Map> { 35 | return mapOf() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ui/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform.ui 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.MutableState 6 | import androidx.compose.runtime.remember 7 | import com.example.data.ElementDto 8 | import com.example.data.ScreenDto 9 | import com.example.data.ViewType 10 | 11 | 12 | class Screen ( screenDto: ScreenDto ) { 13 | var elements = screenDto.children?.map { 14 | it.getComposableElement() 15 | } ?: listOf() 16 | 17 | @Composable 18 | fun compose() { 19 | Column { 20 | val fields = elements.map { it.getHoist() } 21 | Column { 22 | elements.zip(fields).map { 23 | it.first.compose(it.second) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | interface ComposableElement { 31 | @Composable 32 | fun compose(hoist: Map>) 33 | 34 | fun getHoist(): Map> 35 | } 36 | 37 | class EmptyElement : ComposableElement { 38 | @Composable 39 | override fun compose(hoist: Map>) { 40 | } 41 | 42 | override fun getHoist(): Map> { 43 | return mapOf() 44 | } 45 | } 46 | 47 | fun ElementDto.getComposableElement(): ComposableElement { 48 | return when(this.viewtype) { 49 | ViewType.TEXTFIELD -> TextFieldElement(this) 50 | ViewType.TEXT -> TextElement(this) 51 | ViewType.FORM -> FormElement(this) 52 | ViewType.CHECKBOX -> CheckBoxElement(this) 53 | else -> EmptyElement() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ui/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform.ui 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ui/TextElement.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform.ui 2 | 3 | import androidx.compose.foundation.Text 4 | import androidx.compose.material.TextField 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.MutableState 7 | import androidx.compose.runtime.mutableStateOf 8 | import com.example.data.ElementDto 9 | 10 | class TextElement(val elementDto: ElementDto) : ComposableElement { 11 | @Composable 12 | override fun compose(hoist: Map>) { 13 | Text(elementDto.label?:"") 14 | } 15 | 16 | override fun getHoist(): Map> { 17 | return mapOf() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ui/TextFieldElement.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform.ui 2 | 3 | import androidx.compose.foundation.Text 4 | import androidx.compose.material.TextField 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.MutableState 7 | import androidx.compose.runtime.mutableStateOf 8 | import com.example.data.ElementDto 9 | 10 | class TextFieldElement(val elementDto: ElementDto) : ComposableElement { 11 | val fieldName = elementDto.data?:"value" 12 | @Composable 13 | override fun compose(hoist: Map>) { 14 | TextField(value = hoist.get(fieldName)?.value?:"", onValueChange = {hoist.get(fieldName)?.value = it}, label = { Text (elementDto.label?:"") }) 15 | } 16 | 17 | override fun getHoist(): Map> { 18 | return mapOf(Pair(fieldName, mutableStateOf(elementDto.default?:""))) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ui/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform.ui 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | 9 | private val DarkColorPalette = darkColors( 10 | primary = red200, 11 | primaryVariant = red700, 12 | secondary = grey200 13 | ) 14 | 15 | private val LightColorPalette = lightColors( 16 | primary = red500, 17 | primaryVariant = red700, 18 | secondary = grey200 19 | 20 | /* Other default colors to override 21 | background = Color.White, 22 | surface = Color.White, 23 | onPrimary = Color.White, 24 | onSecondary = Color.Black, 25 | onBackground = Color.Black, 26 | onSurface = Color.Black, 27 | */ 28 | ) 29 | 30 | @Composable 31 | fun MyApplicationTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { 32 | val colors = if (darkTheme) { 33 | DarkColorPalette 34 | } else { 35 | LightColorPalette 36 | } 37 | 38 | MaterialTheme( 39 | colors = colors, 40 | typography = typography, 41 | shapes = shapes, 42 | content = content 43 | ) 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/composeform/ui/Type.kt: -------------------------------------------------------------------------------- 1 | package com.example.composeform.ui 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | /* Other default text styles to override 17 | button = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.W500, 20 | fontSize = 14.sp 21 | ), 22 | caption = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 12.sp 26 | ) 27 | */ 28 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politedog/android-compose-form/651f903563d0b7b2e103251150a821973687dd9c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | My Application 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 |