├── .idea ├── .name ├── compiler.xml ├── gradle.xml └── misc.xml ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── rafsan │ │ └── dynamicui_fromjson │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── rafsan │ │ │ └── dynamicui_fromjson │ │ │ ├── GenerateFormActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── data │ │ │ └── CollectData.kt │ │ │ ├── model │ │ │ ├── FormComponent.kt │ │ │ ├── FormComponentItem.kt │ │ │ ├── FormViewComponent.kt │ │ │ ├── Value.kt │ │ │ └── WidgetItems.kt │ │ │ └── utils │ │ │ ├── Utils.kt │ │ │ └── dialog │ │ │ ├── DialogListener.kt │ │ │ └── ShowDialog.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── edit_text_background.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_generate_form.xml │ │ ├── activity_main.xml │ │ ├── dialog_layout.xml │ │ ├── form_buttons_layout.xml │ │ └── form_spinner_row.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── raw │ │ └── sample_json.json │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── rafsan │ └── dynamicui_fromjson │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── data_collect.png ├── form_preview.png ├── formbuilder_web.png └── json_view.png └── settings.gradle /.idea/.name: -------------------------------------------------------------------------------- 1 | DynamicUI-FromJson -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic UI from JSON 2 | 3 | ## Functionality 4 | The app's functionality includes: 5 | 1. The app generates Dynamic UI from JSON. The app support json, generated from https://formbuilder.online/ 6 | 7 | ![](images/formbuilder_web.png) 8 | 9 | 2. If the json is valid, the app will generate a preview of the form. 10 | 11 | 3. Supported widgets: `HEADER, TEXTVIEW, TEXTAREA, SELECT/SPINNER, RADIO-GROUP, PARAGRAPH, DATE, CHECKBOX-GROUP, NUMBER-FIELD, SWITCH-FIELD`. 12 | 13 | 4. User can submit or reset the form. After submit the data collected will be displayed using `JSONArray` properties. 14 | 15 | ## Building 16 | 17 | You can open the project in Android studio and press run. 18 | Android Studio version used to build the project: Arctic fox 2020.3.1 19 | 20 | Gradle plugin used in the project will require `Java 11.0` to run. 21 | 22 | you can set the gradle jdk in `Preferences->Build Tools->Gradle->Gradle JDK` 23 | 24 | ## Screenshots 25 | 26 | Json for the Form | Form generation | Data collect after submit 27 | :------------------------:|:------------------------:|:------------------------ 28 | ![](images/json_view.png) | ![](images/form_preview.png) | ![](images/data_collect.png) 29 | 30 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdk 31 8 | 9 | defaultConfig { 10 | applicationId "com.rafsan.dynamicui_fromjson" 11 | minSdk 21 12 | targetSdk 31 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | buildFeatures { 27 | viewBinding = true 28 | } 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | kotlinOptions { 35 | jvmTarget = '1.8' 36 | } 37 | } 38 | 39 | dependencies { 40 | 41 | implementation 'androidx.core:core-ktx:1.7.0' 42 | implementation 'androidx.appcompat:appcompat:1.4.0' 43 | implementation 'com.google.android.material:material:1.4.0' 44 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2' 45 | implementation 'com.google.code.gson:gson:2.8.9' 46 | 47 | testImplementation 'junit:junit:4.+' 48 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 49 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 50 | } -------------------------------------------------------------------------------- /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/rafsan/dynamicui_fromjson/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson 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.rafsan.dynamicui_fromjson", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/GenerateFormActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.DatePickerDialog 5 | import android.graphics.Color 6 | import android.graphics.Typeface 7 | import android.os.Build 8 | import android.os.Bundle 9 | import android.text.* 10 | import android.text.InputFilter.LengthFilter 11 | import android.text.style.ForegroundColorSpan 12 | import android.text.style.RelativeSizeSpan 13 | import android.util.Log 14 | import android.util.TypedValue 15 | import android.view.* 16 | import android.widget.* 17 | import androidx.appcompat.app.AppCompatActivity 18 | import androidx.appcompat.widget.AppCompatCheckBox 19 | import androidx.appcompat.widget.AppCompatRadioButton 20 | import androidx.appcompat.widget.LinearLayoutCompat 21 | import androidx.appcompat.widget.SwitchCompat 22 | import androidx.core.app.NavUtils 23 | import androidx.core.content.ContextCompat 24 | import com.google.gson.Gson 25 | import com.google.gson.JsonArray 26 | import com.google.gson.JsonObject 27 | import com.rafsan.dynamicui_fromjson.data.CollectData.Companion.getDataFromCheckBoxGroup 28 | import com.rafsan.dynamicui_fromjson.data.CollectData.Companion.getDataFromDateTextView 29 | import com.rafsan.dynamicui_fromjson.data.CollectData.Companion.getDataFromEditText 30 | import com.rafsan.dynamicui_fromjson.data.CollectData.Companion.getDataFromRadioGroup 31 | import com.rafsan.dynamicui_fromjson.data.CollectData.Companion.getDataFromSpinner 32 | import com.rafsan.dynamicui_fromjson.data.CollectData.Companion.printProperties 33 | import com.rafsan.dynamicui_fromjson.databinding.ActivityGenerateFormBinding 34 | import com.rafsan.dynamicui_fromjson.databinding.FormButtonsLayoutBinding 35 | import com.rafsan.dynamicui_fromjson.model.* 36 | import com.rafsan.dynamicui_fromjson.utils.Utils 37 | import com.rafsan.dynamicui_fromjson.utils.Utils.Companion.getCurrentDate 38 | import com.rafsan.dynamicui_fromjson.utils.Utils.Companion.getCustomColorStateList 39 | import com.rafsan.dynamicui_fromjson.utils.Utils.Companion.getDateFromString 40 | import com.rafsan.dynamicui_fromjson.utils.Utils.Companion.getDateStringToShow 41 | import com.rafsan.dynamicui_fromjson.utils.Utils.Companion.method 42 | import com.rafsan.dynamicui_fromjson.utils.Utils.Companion.setMerginToviews 43 | import com.rafsan.dynamicui_fromjson.utils.Utils.Companion.setSwitchColor 44 | import com.rafsan.dynamicui_fromjson.utils.dialog.ShowDialog 45 | import java.util.* 46 | 47 | class GenerateFormActivity : AppCompatActivity() { 48 | 49 | lateinit var binding: ActivityGenerateFormBinding 50 | var formViewCollection: ArrayList = arrayListOf() 51 | 52 | var submitRootJsonObj: JsonObject? = null 53 | 54 | companion object { 55 | var submitPropertyArrayJson: JsonArray? = null 56 | } 57 | 58 | var formComponent: FormComponent? = null 59 | val textColor = Color.parseColor("#000000") 60 | 61 | override fun onCreate(savedInstanceState: Bundle?) { 62 | super.onCreate(savedInstanceState) 63 | binding = ActivityGenerateFormBinding.inflate(layoutInflater) 64 | setContentView(binding.root) 65 | 66 | val json = intent.getStringExtra("value") 67 | submitRootJsonObj = JsonObject() 68 | submitPropertyArrayJson = JsonArray() 69 | json?.let { 70 | populateForm(it) 71 | } 72 | } 73 | 74 | private fun populateForm(json: String) { 75 | formComponent = Gson().fromJson(json, FormComponent::class.java) 76 | var viewId = 1 77 | binding.miniAppFormContainer.visibility = View.VISIBLE 78 | 79 | //TODO:- GENERATE FORM LAYOUT 80 | formComponent?.let { 81 | it.forEach { component -> 82 | when (component.type) { 83 | WidgetItems.HEADER.label -> binding.miniAppFormContainer.addView( 84 | createHeaderView( 85 | component 86 | ) 87 | ) 88 | WidgetItems.TEXT.label -> createEditableTextWithLabel(component, viewId++) 89 | WidgetItems.TEXTAREA.label -> createEditableTextWithLabel(component, viewId++) 90 | WidgetItems.SELECT.label -> createSpinner(component, viewId++) 91 | WidgetItems.RADIO_GROUP.label -> createRadioGroup(component, viewId++) 92 | WidgetItems.PARAGRAPH.label -> createParagraph(component) 93 | WidgetItems.DATE.label -> createDatePicker(component) 94 | WidgetItems.CHECKBOX_GROUP.label -> createCheckBoxGroup(component, viewId++) 95 | WidgetItems.NUMBER.label -> createNumberEditText(component) 96 | } 97 | } 98 | } 99 | addSubmitButtonLayout() 100 | } 101 | 102 | private fun createHeaderView(componentItem: FormComponentItem): TextView { 103 | val txtHeader = TextView(this) 104 | when (componentItem.subtype) { 105 | "h1" -> txtHeader.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f) 106 | "h2" -> txtHeader.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f) 107 | "h3" -> txtHeader.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f) 108 | } 109 | componentItem.label?.let { 110 | txtHeader.text = Utils.fromHtml(it) 111 | } 112 | 113 | txtHeader.layoutParams = LinearLayoutCompat.LayoutParams( 114 | ViewGroup.LayoutParams.MATCH_PARENT, 115 | ViewGroup.LayoutParams.WRAP_CONTENT 116 | ) 117 | txtHeader.setTextColor(textColor) 118 | txtHeader.setPadding(0, 15, 0, 15) 119 | txtHeader.gravity = Gravity.CENTER 120 | return txtHeader 121 | } 122 | 123 | private fun createEditableTextWithLabel(component: FormComponentItem, viewId: Int) { 124 | isLabelNull(component) 125 | val editText = EditText(this) 126 | var rows = 1 127 | 128 | setMerginToviews( 129 | editText, 20, 130 | LinearLayoutCompat.LayoutParams.MATCH_PARENT, 131 | LinearLayout.LayoutParams.WRAP_CONTENT 132 | ) 133 | if (component.type.equals(WidgetItems.TEXTAREA.label)) editText.gravity = Gravity.NO_GRAVITY 134 | 135 | editText.setPadding(20, 30, 20, 30) 136 | editText.setBackgroundResource(R.drawable.edit_text_background) 137 | editText.id = viewId 138 | isValueNull(component, editText) 139 | isSubTypeNull(component, editText) 140 | isPlaceHolderNull(component, editText) 141 | component.maxlength?.let { 142 | editText.filters = arrayOf(LengthFilter(it.toInt())) 143 | } 144 | 145 | component.rows?.let { 146 | rows = it.toInt() 147 | val finalRow = rows 148 | editText.setOnKeyListener { v, keyCode, event -> 149 | (v as EditText).lineCount > finalRow 150 | } 151 | editText.addTextChangedListener(object : TextWatcher { 152 | override fun beforeTextChanged( 153 | s: CharSequence, start: Int, count: Int, after: Int 154 | ) { 155 | } 156 | 157 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} 158 | override fun afterTextChanged(s: Editable) { 159 | if (editText.lineCount > finalRow) { 160 | editText.setText(method(editText.text.toString())) 161 | editText.setSelection(editText.text.toString().length) 162 | } 163 | } 164 | }) 165 | } 166 | editText.setLines(rows) 167 | binding.miniAppFormContainer.addView(editText) 168 | formViewCollection.add(FormViewComponent(editText, component)) 169 | Log.i("EditTextInputType", editText.inputType.toString() + "") 170 | } 171 | 172 | private fun createSpinner(component: FormComponentItem, viewId: Int) { 173 | var selectedIndex = 0 174 | createLabelForViews(component) 175 | val layoutParams: LinearLayout.LayoutParams = LinearLayout.LayoutParams( 176 | LinearLayout.LayoutParams.MATCH_PARENT, 177 | LinearLayout.LayoutParams.WRAP_CONTENT 178 | ) 179 | layoutParams.setMargins(40, 30, 40, 40) 180 | val spinner = Spinner(this) 181 | spinner.id = viewId 182 | spinner.setBackgroundColor(Color.WHITE) 183 | spinner.setBackgroundResource(R.drawable.edit_text_background) 184 | spinner.layoutParams = layoutParams 185 | //Spinner data source 186 | val spinnerDatasource = mutableListOf() 187 | component.values?.let { 188 | for (j in it.indices) { 189 | val value = it[j] 190 | spinnerDatasource.add(value.label) 191 | value.selected?.let { selected -> 192 | if (selected) 193 | selectedIndex = j 194 | } 195 | } 196 | 197 | val spinnerAdapter: ArrayAdapter<*> = ArrayAdapter( 198 | applicationContext, 199 | R.layout.form_spinner_row, spinnerDatasource 200 | ) 201 | spinner.adapter = spinnerAdapter 202 | spinner.setSelection(selectedIndex) 203 | } 204 | binding.miniAppFormContainer.addView(spinner) 205 | formViewCollection.add(FormViewComponent(spinner, component)) 206 | } 207 | 208 | @SuppressLint("RestrictedApi") 209 | private fun createRadioGroup(component: FormComponentItem, viewId: Int) { 210 | createLabelForViews(component) 211 | var selectedItem = 0 212 | var isRadioButtonSelected = false 213 | 214 | val radioGroup = RadioGroup(this) 215 | radioGroup.id = viewId 216 | Log.i("RadioGroupId", radioGroup.id.toString() + " " + radioGroup.tag) 217 | radioGroup.orientation = LinearLayout.VERTICAL 218 | setMerginToviews(radioGroup) 219 | 220 | component.values?.let { 221 | for (i in it.indices) { 222 | val value = it[i] 223 | val radioButton = AppCompatRadioButton(this) 224 | radioButton.text = value.label 225 | radioButton.supportButtonTintList = getCustomColorStateList(this) 226 | 227 | value.selected?.let { selected -> 228 | if (selected) { 229 | selectedItem = i 230 | isRadioButtonSelected = true 231 | } 232 | } 233 | radioGroup.addView(radioButton) 234 | } 235 | if (component.toggle != null && component.toggle) { 236 | val radioGroupContainer = RelativeLayout(this) 237 | var valueModels: MutableList = mutableListOf() 238 | component.values.let { options -> 239 | valueModels = options as MutableList 240 | } 241 | val valueModel = Value("Other", null, null) 242 | valueModels.add(valueModel) 243 | val radioButton = AppCompatRadioButton(this) 244 | radioButton.text = valueModel.label 245 | radioButton.supportButtonTintList = getCustomColorStateList(this) 246 | radioGroup.addView(radioButton) 247 | radioGroupContainer.addView(radioGroup) 248 | val otherText = EditText(this) 249 | otherText.setBackgroundResource(R.drawable.edit_text_background) 250 | otherText.isEnabled = false 251 | val otherTextParam = RelativeLayout.LayoutParams( 252 | RelativeLayout.LayoutParams.MATCH_PARENT, 253 | RelativeLayout.LayoutParams.WRAP_CONTENT 254 | ) 255 | otherTextParam.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) 256 | otherTextParam.addRule(RelativeLayout.RIGHT_OF, radioGroup.id) 257 | //otherTextParam.setMargins(10, 0, 40, 0); 258 | otherText.layoutParams = otherTextParam 259 | otherText.setPadding(10, 8, 10, 8) 260 | otherText.maxLines = 1 261 | radioGroupContainer.addView(otherText) 262 | otherText.addTextChangedListener(object : TextWatcher { 263 | override fun beforeTextChanged( 264 | s: CharSequence, start: Int, count: Int, after: Int 265 | ) { 266 | } 267 | 268 | override fun onTextChanged( 269 | s: CharSequence, start: Int, before: Int, count: Int 270 | ) { 271 | } 272 | 273 | override fun afterTextChanged(s: Editable) { 274 | if (otherText.text.toString() != "") { 275 | valueModel.value = otherText.text.toString() 276 | } 277 | } 278 | }) 279 | radioButton.setOnCheckedChangeListener { buttonView, isChecked -> 280 | otherText.isEnabled = isChecked 281 | } 282 | binding.miniAppFormContainer.addView(radioGroupContainer) 283 | formViewCollection.add(FormViewComponent(radioGroup, component)) 284 | } else { 285 | binding.miniAppFormContainer.addView(radioGroup) 286 | formViewCollection.add(FormViewComponent(radioGroup, component)) 287 | } 288 | if (isRadioButtonSelected) (radioGroup.getChildAt(selectedItem) as RadioButton).isChecked = 289 | true 290 | } 291 | } 292 | 293 | @SuppressLint("SetTextI18n") 294 | private fun createParagraph(component: FormComponentItem) { 295 | val textView = TextView(this) 296 | 297 | val paragraphText: String = 298 | component.label?.substring(0, component.label.length - 2) ?: "" 299 | 300 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 301 | if (component.subtype.equals("blockquote")) { 302 | textView.text = 303 | "\"" + Html.fromHtml(paragraphText, Html.FROM_HTML_MODE_LEGACY) + "\"" 304 | textView.setTypeface(null, Typeface.ITALIC) 305 | } else textView.text = Html.fromHtml(paragraphText, Html.FROM_HTML_MODE_LEGACY) 306 | } else { 307 | if (component.subtype.equals("blockquote")) { 308 | textView.text = "\"" + Html.fromHtml(paragraphText + "\"") 309 | textView.setTypeface(null, Typeface.ITALIC) 310 | } else textView.text = Html.fromHtml(paragraphText) 311 | } 312 | 313 | textView.setTextColor(textColor) 314 | setMerginToviews(textView) 315 | textView.setPadding(0, 10, 0, 10) 316 | binding.miniAppFormContainer.addView(textView) 317 | } 318 | 319 | private fun createDatePicker(component: FormComponentItem) { 320 | val relativeLayout = RelativeLayout(this) 321 | relativeLayout.setPadding(5, 10, 5, 10) 322 | val layoutParams = LinearLayout.LayoutParams( 323 | RelativeLayout.LayoutParams.MATCH_PARENT, 324 | RelativeLayout.LayoutParams.WRAP_CONTENT 325 | ) 326 | layoutParams.setMargins(40, 40, 40, 40) 327 | relativeLayout.layoutParams = layoutParams 328 | 329 | component.label?.let { labelString -> 330 | val textView = TextView(this) 331 | textView.setTextColor(Color.BLACK) 332 | textView.setTypeface(null, Typeface.BOLD) 333 | component.required?.let { required -> 334 | if (required) { 335 | textView.text = labelStringForRequiredField(labelString) 336 | } else { 337 | textView.text = createStringForViewLabel(false, labelString) 338 | } 339 | } 340 | val layoutParams1 = RelativeLayout.LayoutParams( 341 | RelativeLayout.LayoutParams.WRAP_CONTENT, 342 | RelativeLayout.LayoutParams.WRAP_CONTENT 343 | ) 344 | layoutParams1.addRule(RelativeLayout.ALIGN_PARENT_LEFT) 345 | layoutParams1.addRule(RelativeLayout.CENTER_VERTICAL) 346 | relativeLayout.addView(textView) 347 | } 348 | 349 | val txtDate = TextView(this) 350 | val layoutParams1 = RelativeLayout.LayoutParams( 351 | RelativeLayout.LayoutParams.WRAP_CONTENT, 352 | RelativeLayout.LayoutParams.WRAP_CONTENT 353 | ) 354 | layoutParams1.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) 355 | layoutParams1.addRule(RelativeLayout.CENTER_VERTICAL) 356 | txtDate.layoutParams = layoutParams1 357 | relativeLayout.addView(txtDate) 358 | 359 | txtDate.text = getDateStringToShow(getCurrentDate(), "MMMM d, yyyy") 360 | 361 | relativeLayout.setOnClickListener { 362 | val calendar: Calendar = GregorianCalendar() 363 | val dateString = getDateFromString(txtDate.text.toString(), "MMMM d, yyyy") 364 | dateString?.let { 365 | calendar.time = it 366 | val datePickerDialog = DatePickerDialog( 367 | this@GenerateFormActivity, 368 | { view, year, month, dayOfMonth -> 369 | val selectedDate = Calendar.getInstance() 370 | selectedDate[year, month] = dayOfMonth 371 | txtDate.text = getDateStringToShow(selectedDate.time, "MMMM d, yyyy") 372 | }, calendar[Calendar.YEAR], calendar[Calendar.MONTH], 373 | calendar[Calendar.DAY_OF_MONTH] 374 | ) 375 | datePickerDialog.show() 376 | } 377 | } 378 | binding.miniAppFormContainer.addView(relativeLayout) 379 | formViewCollection.add(FormViewComponent(txtDate, component)) 380 | } 381 | 382 | private fun createCheckBoxGroup(component: FormComponentItem, viewId: Int) { 383 | isLabelNull(component) 384 | if (component.toggle != null && component.toggle) { 385 | createToggleCheckBoxGroup(component, viewId) 386 | } else createCheckBoxGroupUtil(component, viewId) 387 | } 388 | 389 | private fun createNumberEditText(component: FormComponentItem) { 390 | var minValue = 0 391 | var maxValue = 0L 392 | var step = 1 393 | component.min?.let { 394 | minValue = it 395 | } 396 | component.max?.let { 397 | maxValue = it 398 | } 399 | component.step?.let { 400 | step = it 401 | } 402 | val finalStep = step 403 | val finalMinValue = minValue 404 | val finalMaxValue = maxValue 405 | 406 | isLabelNull(component) 407 | val numberViewContainer = LinearLayout(this) 408 | val layoutParams = LinearLayout.LayoutParams( 409 | LinearLayout.LayoutParams.MATCH_PARENT, 410 | LinearLayout.LayoutParams.WRAP_CONTENT 411 | ) 412 | numberViewContainer.setPadding(40, 10, 10, 10) 413 | numberViewContainer.layoutParams = layoutParams 414 | 415 | val editTextParam = LinearLayout.LayoutParams( 416 | 0, 417 | LinearLayout.LayoutParams.WRAP_CONTENT 418 | ) 419 | editTextParam.weight = 5f 420 | editTextParam.setMargins(0, 0, 20, 0) 421 | val editText = EditText(this) 422 | editText.layoutParams = editTextParam 423 | editText.setPadding(10, 10, 10, 10) 424 | editText.inputType = InputType.TYPE_CLASS_NUMBER 425 | editText.setBackgroundResource(R.drawable.edit_text_background) 426 | component.placeholder?.let { 427 | editText.hint = it 428 | } 429 | 430 | editText.addTextChangedListener(object : TextWatcher { 431 | override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} 432 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { 433 | if (s.length > 1) { 434 | if (finalMaxValue != 0L) { 435 | if (s.toString().toLong() > finalMaxValue) { 436 | editText.setText(finalMaxValue.toString()) 437 | } 438 | } 439 | if (finalMinValue != 0) { 440 | if (s.toString().toLong() < finalMinValue) { 441 | editText.setText(finalMinValue.toString()) 442 | } 443 | } 444 | } 445 | } 446 | 447 | override fun afterTextChanged(s: Editable) { 448 | } 449 | }) 450 | 451 | isValueNull(component, editText) 452 | editText.setText(minValue.toString()) 453 | isSubTypeNull(component, editText) 454 | isPlaceHolderNull(component, editText) 455 | 456 | component.maxlength?.let { 457 | editText.filters = arrayOf(LengthFilter(it.toInt())) 458 | } 459 | 460 | val textParams = LinearLayout.LayoutParams( 461 | 0, 462 | LinearLayout.LayoutParams.MATCH_PARENT 463 | ) 464 | textParams.weight = 1f 465 | textParams.setMargins(10, 0, 5, 0) 466 | val negativeButton = TextView(this) 467 | negativeButton.isAllCaps = false 468 | negativeButton.text = "-" 469 | negativeButton.gravity = Gravity.CENTER 470 | negativeButton.setTextColor(textColor) 471 | negativeButton.layoutParams = textParams 472 | negativeButton.setBackgroundColor(ContextCompat.getColor(this, R.color.teal_500)) 473 | 474 | val positiveButton = TextView(this) 475 | positiveButton.isAllCaps = false 476 | positiveButton.text = "+" 477 | positiveButton.gravity = Gravity.CENTER 478 | positiveButton.setTextColor(textColor) 479 | positiveButton.layoutParams = textParams 480 | positiveButton.setBackgroundColor(ContextCompat.getColor(this, R.color.teal_500)) 481 | 482 | //ClickListener for negative button 483 | negativeButton.setOnClickListener { v: View? -> 484 | var editTextNumber = 0 485 | if (editText.text.toString() != "") { 486 | editTextNumber = editText.text.toString().toInt() 487 | } 488 | Log.i("NumberFiledValue", editTextNumber.toString()) 489 | if (finalStep == 0) { 490 | if (editTextNumber == finalMinValue) { 491 | editText.setText(finalMaxValue.toString()) 492 | } else { 493 | editTextNumber-- 494 | editText.setText(editTextNumber.toString()) 495 | } 496 | } else { 497 | if (editTextNumber == finalMinValue || editTextNumber - finalStep < finalMinValue) { 498 | editText.setText(finalMaxValue.toString()) 499 | } else { 500 | editTextNumber -= finalStep 501 | editText.setText(editTextNumber.toString()) 502 | } 503 | } 504 | } 505 | 506 | //ClickListener for positive button 507 | positiveButton.setOnClickListener { v: View? -> 508 | var editTextNumber = 0L 509 | if (editText.text.toString() != "") { 510 | editTextNumber = editText.text.toString().toLong() 511 | } 512 | Log.i("NumberFieldValue", editTextNumber.toString()) 513 | if (finalStep == 0) { 514 | if (editTextNumber == finalMaxValue) { 515 | editText.setText(finalMinValue.toString()) 516 | } else { 517 | editTextNumber++ 518 | editText.setText(editTextNumber.toString()) 519 | } 520 | } else { 521 | if (editTextNumber == finalMaxValue || editTextNumber + finalStep > finalMaxValue) { 522 | editText.setText(finalMinValue.toString()) 523 | } else { 524 | editTextNumber += finalStep 525 | editText.setText(editTextNumber.toString()) 526 | } 527 | } 528 | } 529 | 530 | numberViewContainer.addView(editText) 531 | numberViewContainer.addView(negativeButton) 532 | numberViewContainer.addView(positiveButton) 533 | binding.miniAppFormContainer.addView(numberViewContainer) 534 | formViewCollection.add(FormViewComponent(editText, component)) 535 | } 536 | 537 | @SuppressLint("RestrictedApi") 538 | private fun createCheckBoxGroupUtil(component: FormComponentItem, id: Int) { 539 | val checkBoxContainer = LinearLayout(this) 540 | checkBoxContainer.id = id 541 | checkBoxContainer.orientation = LinearLayout.VERTICAL 542 | val layoutParams = LinearLayout.LayoutParams( 543 | LinearLayout.LayoutParams.WRAP_CONTENT, 544 | LinearLayout.LayoutParams.WRAP_CONTENT 545 | ) 546 | checkBoxContainer.layoutParams = layoutParams 547 | component.values?.let { 548 | for (i in it.indices) { 549 | val valueModel = it[i] 550 | val checkBox = AppCompatCheckBox(this) 551 | checkBox.text = valueModel.label 552 | val layoutParams1 = LinearLayout.LayoutParams( 553 | LinearLayout.LayoutParams.MATCH_PARENT, 554 | LinearLayout.LayoutParams.WRAP_CONTENT 555 | ) 556 | layoutParams1.setMargins(70, 20, 20, 0) 557 | checkBox.layoutParams = layoutParams1 558 | valueModel.selected?.let { selected -> 559 | if (selected) { 560 | checkBox.isChecked = true 561 | } 562 | } 563 | checkBox.supportButtonTintList = getCustomColorStateList(this) 564 | checkBoxContainer.addView(checkBox) 565 | } 566 | if (component.toggle != null && component.toggle) { 567 | val rootContainer = RelativeLayout(this) 568 | var valueModels: MutableList = mutableListOf() 569 | component.values.let { options -> 570 | valueModels = options as MutableList 571 | } 572 | val valueModel = Value("Other", null, null) 573 | valueModels.add(valueModel) 574 | val checkBox = AppCompatCheckBox(this) 575 | checkBox.text = valueModel.label 576 | setMerginToviews( 577 | checkBox, 20, 578 | LinearLayout.LayoutParams.MATCH_PARENT, 579 | LinearLayout.LayoutParams.WRAP_CONTENT 580 | ) 581 | valueModel.selected?.let { selected -> 582 | if (selected) { 583 | checkBox.isChecked = true 584 | } 585 | } 586 | 587 | checkBox.supportButtonTintList = getCustomColorStateList(this) 588 | checkBoxContainer.addView(checkBox) 589 | rootContainer.addView(checkBoxContainer) 590 | val otherText = EditText(this) 591 | otherText.setBackgroundResource(R.drawable.edit_text_background) 592 | otherText.isEnabled = false 593 | val otherTextParam = RelativeLayout.LayoutParams( 594 | RelativeLayout.LayoutParams.MATCH_PARENT, 595 | RelativeLayout.LayoutParams.WRAP_CONTENT 596 | ) 597 | otherTextParam.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) 598 | otherTextParam.addRule(RelativeLayout.RIGHT_OF, checkBoxContainer.id) 599 | otherTextParam.setMargins(10, 0, 40, 0) 600 | otherText.layoutParams = otherTextParam 601 | otherText.setPadding(10, 8, 10, 8) 602 | otherText.maxLines = 1 603 | rootContainer.addView(otherText) 604 | otherText.addTextChangedListener(object : TextWatcher { 605 | override fun beforeTextChanged( 606 | s: CharSequence, start: Int, count: Int, after: Int 607 | ) { 608 | } 609 | 610 | override fun onTextChanged( 611 | s: CharSequence, start: Int, before: Int, count: Int 612 | ) { 613 | } 614 | 615 | override fun afterTextChanged(s: Editable) { 616 | if (otherText.text.toString() != "") { 617 | valueModel.value = otherText.text.toString() 618 | } 619 | } 620 | }) 621 | checkBox.setOnCheckedChangeListener { buttonView, isChecked -> 622 | otherText.isEnabled = isChecked 623 | } 624 | binding.miniAppFormContainer.addView(rootContainer) 625 | formViewCollection.add(FormViewComponent(checkBoxContainer, component)) 626 | } else { 627 | binding.miniAppFormContainer.addView(checkBoxContainer) 628 | formViewCollection.add(FormViewComponent(checkBoxContainer, component)) 629 | } 630 | } 631 | } 632 | 633 | /** 634 | * Checkbox group with Switch 635 | */ 636 | private fun createToggleCheckBoxGroup(viewComponentModel: FormComponentItem, id: Int) { 637 | val switchContainer = LinearLayout(this) 638 | switchContainer.id = id 639 | switchContainer.orientation = LinearLayout.VERTICAL 640 | val layoutParams = LinearLayout.LayoutParams( 641 | LinearLayout.LayoutParams.WRAP_CONTENT, 642 | LinearLayout.LayoutParams.WRAP_CONTENT 643 | ) 644 | switchContainer.layoutParams = layoutParams 645 | viewComponentModel.values?.let { 646 | for (i in it.indices) { 647 | val valueModel = it[i] 648 | val mySwitch = SwitchCompat(this) 649 | mySwitch.text = valueModel.label 650 | val layoutParams1 = LinearLayout.LayoutParams( 651 | LinearLayout.LayoutParams.MATCH_PARENT, 652 | LinearLayout.LayoutParams.WRAP_CONTENT 653 | ) 654 | layoutParams1.setMargins(80, 20, 20, 0) 655 | mySwitch.layoutParams = layoutParams1 656 | valueModel.selected?.let { selected -> 657 | if (selected) { 658 | mySwitch.isChecked = true 659 | } 660 | } 661 | setSwitchColor(mySwitch, this) 662 | switchContainer.addView(mySwitch) 663 | } 664 | if (viewComponentModel.toggle != null && viewComponentModel.toggle) { 665 | val rootContainer = RelativeLayout(this) 666 | val valueModels: MutableList = mutableListOf() 667 | val valueModel = Value("Other", null, null) 668 | valueModels.add(valueModel) 669 | val mySwitch = SwitchCompat(this) 670 | mySwitch.text = valueModel.label 671 | val layoutParams1 = LinearLayout.LayoutParams( 672 | LinearLayout.LayoutParams.MATCH_PARENT, 673 | LinearLayout.LayoutParams.WRAP_CONTENT 674 | ) 675 | layoutParams1.setMargins(80, 20, 20, 0) 676 | mySwitch.layoutParams = layoutParams1 677 | valueModel.selected?.let { selected -> 678 | if (selected) { 679 | mySwitch.isChecked = true 680 | } 681 | } 682 | setSwitchColor(mySwitch, this) 683 | switchContainer.addView(mySwitch) 684 | rootContainer.addView(switchContainer) 685 | val otherText = EditText(this) 686 | otherText.setBackgroundResource(R.drawable.edit_text_background) 687 | otherText.isEnabled = false 688 | val otherTextParam = RelativeLayout.LayoutParams( 689 | RelativeLayout.LayoutParams.MATCH_PARENT, 690 | RelativeLayout.LayoutParams.WRAP_CONTENT 691 | ) 692 | otherTextParam.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) 693 | otherTextParam.addRule(RelativeLayout.RIGHT_OF, switchContainer.id) 694 | otherTextParam.setMargins(10, 0, 40, 0) 695 | otherText.layoutParams = otherTextParam 696 | otherText.setPadding(10, 8, 10, 8) 697 | otherText.maxLines = 1 698 | rootContainer.addView(otherText) 699 | otherText.addTextChangedListener(object : TextWatcher { 700 | override fun beforeTextChanged( 701 | s: CharSequence, start: Int, count: Int, after: Int 702 | ) { 703 | } 704 | 705 | override fun onTextChanged( 706 | s: CharSequence, start: Int, before: Int, count: Int 707 | ) { 708 | } 709 | 710 | override fun afterTextChanged(s: Editable) { 711 | if (otherText.text.toString() != "") { 712 | valueModel.value = otherText.text.toString() 713 | } 714 | } 715 | }) 716 | mySwitch.setOnCheckedChangeListener { buttonView, isChecked -> 717 | otherText.isEnabled = isChecked 718 | } 719 | binding.miniAppFormContainer.addView(rootContainer) 720 | formViewCollection.add(FormViewComponent(switchContainer, viewComponentModel)) 721 | } else { 722 | binding.miniAppFormContainer.addView(switchContainer) 723 | formViewCollection.add(FormViewComponent(switchContainer, viewComponentModel)) 724 | } 725 | } 726 | } 727 | 728 | private fun isLabelNull(viewComponentModel: FormComponentItem) { 729 | if (viewComponentModel.label != null) createLabelForViews(viewComponentModel) 730 | } 731 | 732 | private fun isSubTypeNull(viewComponentModel: FormComponentItem, editText: EditText) { 733 | if (viewComponentModel.subtype != null) { 734 | setInputTypeForEditText(editText, viewComponentModel) 735 | } 736 | } 737 | 738 | private fun isPlaceHolderNull( 739 | viewComponentModel: FormComponentItem, 740 | editText: EditText 741 | ) { 742 | if (viewComponentModel.placeholder != null) editText.hint = viewComponentModel.placeholder 743 | } 744 | 745 | private fun isValueNull(viewComponentModel: FormComponentItem, view: TextView) { 746 | viewComponentModel.value?.let { 747 | view.text = it 748 | } 749 | } 750 | 751 | private fun createLabelForViews(viewComponentModel: FormComponentItem) { 752 | val label = TextView(this) 753 | label.setTextColor(Color.BLACK) 754 | label.setTypeface(null, Typeface.BOLD) 755 | setMerginToviews( 756 | label, 757 | 40, 758 | LinearLayoutCompat.LayoutParams.WRAP_CONTENT, 759 | LinearLayoutCompat.LayoutParams.WRAP_CONTENT 760 | ) 761 | viewComponentModel.label?.let { labelText -> 762 | viewComponentModel.required?.let { 763 | if (it) { 764 | label.text = createStringForViewLabel(it, labelText) 765 | } else { 766 | label.text = createStringForViewLabel(false, labelText) 767 | } 768 | } 769 | binding.miniAppFormContainer.addView(label) 770 | } 771 | } 772 | 773 | /** 774 | * EditText Input type selection 775 | */ 776 | private fun setInputTypeForEditText( 777 | editText: EditText, 778 | viewComponentModel: FormComponentItem 779 | ) { 780 | when (viewComponentModel.subtype) { 781 | "password" -> editText.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD 782 | "email" -> editText.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 783 | "tel" -> editText.inputType = InputType.TYPE_CLASS_PHONE 784 | "dateTimeLocal" -> editText.inputType = InputType.TYPE_CLASS_DATETIME 785 | else -> editText.inputType = InputType.TYPE_CLASS_TEXT 786 | } 787 | } 788 | 789 | private fun createStringForViewLabel( 790 | required: Boolean, 791 | label: String 792 | ): SpannableStringBuilder { 793 | val labelStr = Utils.fromHtml(label) 794 | return if (required) { 795 | labelStringForRequiredField(labelStr) 796 | } else { 797 | val username = SpannableString(labelStr) 798 | val description = SpannableStringBuilder() 799 | username.setSpan( 800 | RelativeSizeSpan(1.1f), 0, username.length, 801 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 802 | ) 803 | username.setSpan( 804 | ForegroundColorSpan(textColor), 0, username.length, 805 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 806 | ) 807 | description.append(username) 808 | description 809 | } 810 | } 811 | 812 | private fun labelStringForRequiredField(label: String): SpannableStringBuilder { 813 | val username = SpannableString(label) 814 | val description = SpannableStringBuilder() 815 | username.setSpan( 816 | RelativeSizeSpan(1.1f), 0, username.length, 817 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 818 | ) 819 | username.setSpan( 820 | ForegroundColorSpan(textColor), 0, username.length, 821 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 822 | ) 823 | description.append(username) 824 | val commentSpannable = SpannableString(" *") 825 | commentSpannable.setSpan( 826 | ForegroundColorSpan(Color.RED), 0, 827 | commentSpannable.length, 828 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 829 | ) 830 | commentSpannable.setSpan( 831 | RelativeSizeSpan(1.0f), 0, 832 | commentSpannable.length, 833 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 834 | ) 835 | description.append(commentSpannable) 836 | return description 837 | } 838 | 839 | private fun addSubmitButtonLayout() { 840 | val layoutInflater = LayoutInflater.from(this) 841 | val buttonViewBinding: FormButtonsLayoutBinding = 842 | FormButtonsLayoutBinding.inflate(layoutInflater) 843 | binding.miniAppFormContainer.addView(buttonViewBinding.root) 844 | buttonViewBinding.btnReset.setOnClickListener { 845 | startActivity(intent) 846 | finish() 847 | } 848 | 849 | buttonViewBinding.btnSubmit.setOnClickListener { 850 | for (formViewComponent in formViewCollection) { 851 | val view: View = formViewComponent.createdView 852 | val viewComponentModel: FormComponentItem = 853 | formViewComponent.getViewComponentModel() 854 | when (viewComponentModel.type) { 855 | WidgetItems.TEXT.label -> 856 | if (!getDataFromEditText(view, viewComponentModel)) { 857 | viewComponentModel.label?.let { labelStr -> 858 | showRequiredDialog(labelStr) 859 | } 860 | } 861 | WidgetItems.TEXTAREA.label -> 862 | if (!getDataFromEditText(view, viewComponentModel)) { 863 | viewComponentModel.label?.let { labelStr -> 864 | showRequiredDialog(labelStr) 865 | } 866 | } 867 | WidgetItems.SELECT.label -> 868 | if (!getDataFromSpinner(view, viewComponentModel)) { 869 | viewComponentModel.label?.let { labelStr -> 870 | showRequiredDialog(labelStr) 871 | } 872 | } 873 | WidgetItems.RADIO_GROUP.label -> 874 | if (!getDataFromRadioGroup(view, viewComponentModel)) { 875 | viewComponentModel.label?.let { labelStr -> 876 | showRequiredDialog(labelStr) 877 | } 878 | } 879 | WidgetItems.DATE.label -> getDataFromDateTextView(view, viewComponentModel) 880 | WidgetItems.CHECKBOX_GROUP.label -> 881 | if (!getDataFromCheckBoxGroup(view, viewComponentModel)) { 882 | viewComponentModel.label?.let { labelStr -> 883 | showRequiredDialog(labelStr) 884 | } 885 | } 886 | WidgetItems.NUMBER.label -> { 887 | if (!getDataFromEditText(view, viewComponentModel)) { 888 | viewComponentModel.label?.let { labelStr -> 889 | showRequiredDialog(labelStr) 890 | } 891 | } 892 | } 893 | } 894 | } 895 | 896 | submitRootJsonObj?.add("properties", submitPropertyArrayJson) 897 | Log.i("JsonArray", submitRootJsonObj.toString()) 898 | submitPropertyArrayJson?.let { 899 | val collectedData = printProperties(it) 900 | ShowDialog.customDialog(this, "Collected Data", collectedData, null) 901 | submitPropertyArrayJson = JsonArray() 902 | } 903 | } 904 | } 905 | 906 | private fun showRequiredDialog(labelStr: String) { 907 | ShowDialog.customDialog( 908 | this, 909 | "Required", 910 | labelStr, null 911 | ) 912 | } 913 | 914 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 915 | // Respond to the action bar's Up/Home button 916 | if (item.itemId == R.id.home) { 917 | NavUtils.navigateUpFromSameTask(this) 918 | return true 919 | } 920 | return super.onOptionsItemSelected(item) 921 | } 922 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.google.android.material.snackbar.Snackbar 7 | import com.google.gson.Gson 8 | import com.rafsan.dynamicui_fromjson.databinding.ActivityMainBinding 9 | 10 | class MainActivity : AppCompatActivity() { 11 | 12 | lateinit var binding: ActivityMainBinding 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | binding = ActivityMainBinding.inflate(layoutInflater) 17 | setContentView(binding.root) 18 | val json = readRawJson() 19 | 20 | binding.buttonView.setOnClickListener { 21 | //Generate form 22 | if (json.isNotEmpty()) { 23 | val intent = Intent(this, GenerateFormActivity::class.java) 24 | intent.putExtra("value", json) 25 | startActivity(intent) 26 | } 27 | } 28 | } 29 | 30 | fun readRawJson(): String { 31 | val text = resources.openRawResource(R.raw.sample_json) 32 | .bufferedReader().use { it.readText() } 33 | val check = checkValidity(text) 34 | if (check) { 35 | binding.etContent.setText(text) 36 | } else { 37 | Snackbar.make(binding.content, "Invalid JSON", Snackbar.LENGTH_SHORT).show() 38 | return "" 39 | } 40 | return text 41 | } 42 | 43 | fun checkValidity(string: String): Boolean { 44 | val gson = Gson() 45 | try { 46 | gson.fromJson(string, Object::class.java) 47 | } catch (e: Exception) { 48 | return false 49 | } 50 | return true 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/data/CollectData.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson.data 2 | 3 | import android.util.Log 4 | import android.view.View 5 | import android.widget.* 6 | import androidx.appcompat.widget.SwitchCompat 7 | import com.google.gson.JsonArray 8 | import com.google.gson.JsonObject 9 | import com.rafsan.dynamicui_fromjson.GenerateFormActivity.Companion.submitPropertyArrayJson 10 | import com.rafsan.dynamicui_fromjson.model.FormComponentItem 11 | import com.rafsan.dynamicui_fromjson.model.Value 12 | import com.rafsan.dynamicui_fromjson.utils.Utils.Companion.isValidEmailAddress 13 | import com.rafsan.dynamicui_fromjson.utils.Utils.Companion.isValidTelephoneNumber 14 | import java.util.* 15 | 16 | class CollectData { 17 | companion object { 18 | /** 19 | * Get selected Date from date TextView. 20 | * 21 | * @param view 22 | * @param FormComponentItem 23 | */ 24 | fun getDataFromDateTextView( 25 | view: View, 26 | viewComponentModel: FormComponentItem 27 | ) { 28 | val dateView = view as TextView 29 | val json = populateSubmitPropertyJson(viewComponentModel, null) 30 | json.addProperty("value", dateView.text.toString()) 31 | submitPropertyArrayJson?.add(json) 32 | } 33 | 34 | /** 35 | * @param view 36 | * @param FormComponentItem 37 | * @return 38 | */ 39 | fun getDataFromCheckBoxGroup( 40 | view: View, 41 | viewComponentModel: FormComponentItem 42 | ): Boolean { 43 | return if (viewComponentModel.toggle != null && viewComponentModel.toggle) { 44 | getDataFromSwitchContainer(view, viewComponentModel) 45 | } else getDataFromCheckBoxContainer(view, viewComponentModel) 46 | } 47 | 48 | /** 49 | * @param view 50 | * @param FormComponentItem 51 | * @return 52 | */ 53 | fun getDataFromCheckBoxContainer( 54 | view: View, 55 | viewComponentModel: FormComponentItem 56 | ): Boolean { 57 | val checkBoxContainer = view as LinearLayout 58 | val submitJsonValues = JsonArray() 59 | var valueModel: Value? 60 | var isChecked = false 61 | return if (viewComponentModel.required != null && viewComponentModel.required) { 62 | for (i in 0 until checkBoxContainer.childCount) { 63 | val submitJsonValue = JsonObject() 64 | val checkBox = checkBoxContainer.getChildAt(i) as CheckBox 65 | valueModel = viewComponentModel.values?.get(i) 66 | if (checkBox.isChecked) { 67 | submitJsonValue.addProperty("label", checkBox.text.toString()) 68 | //submitJsonValue.addProperty("value", valueModel.getValue()); 69 | if (valueModel != null) { 70 | if (valueModel.label == "Other") { 71 | submitJsonValue.addProperty("value", valueModel.value) 72 | } else submitJsonValue.addProperty("value", checkBox.text.toString()) 73 | } 74 | submitJsonValues.add(submitJsonValue) 75 | isChecked = true 76 | } 77 | } 78 | val json = populateSubmitPropertyJson(viewComponentModel, null) 79 | json.add("value", submitJsonValues) 80 | submitPropertyArrayJson?.add(json) 81 | return isChecked 82 | } else { 83 | for (i in 0 until checkBoxContainer.childCount) { 84 | val submitJsonValue = JsonObject() 85 | val checkBox = checkBoxContainer.getChildAt(i) as CheckBox 86 | valueModel = viewComponentModel.values?.get(i) 87 | if (checkBox.isChecked) { 88 | submitJsonValue.addProperty("label", checkBox.text.toString()) 89 | //submitJsonValue.addProperty("value", valueModel.getValue()); 90 | if (valueModel != null) { 91 | if (valueModel.label.equals("Other")) { 92 | submitJsonValue.addProperty("value", valueModel.value) 93 | } else submitJsonValue.addProperty("value", checkBox.text.toString()) 94 | } 95 | submitJsonValues.add(submitJsonValue) 96 | } 97 | } 98 | val json = populateSubmitPropertyJson(viewComponentModel, null) 99 | json.add("value", submitJsonValues) 100 | submitPropertyArrayJson?.add(json) 101 | true 102 | } 103 | } 104 | 105 | /** 106 | * @param view 107 | * @param FormComponentItem 108 | * @return 109 | */ 110 | fun getDataFromSwitchContainer( 111 | view: View, 112 | viewComponentModel: FormComponentItem 113 | ): Boolean { 114 | val checkBoxContainer = view as LinearLayout 115 | val submitJsonValues = JsonArray() 116 | var valueModel: Value? 117 | var isChecked = false 118 | return if (viewComponentModel.required != null && viewComponentModel.required) { 119 | for (i in 0 until (viewComponentModel.values?.size ?: 0)) { 120 | val submitJsonValue = JsonObject() 121 | val aSwitch = checkBoxContainer.getChildAt(i) as SwitchCompat 122 | valueModel = viewComponentModel.values?.get(i) 123 | if (aSwitch.isChecked) { 124 | submitJsonValue.addProperty("label", aSwitch.text.toString()) 125 | //submitJsonValue.addProperty("value", valueModel.getValue()); 126 | if (valueModel != null) { 127 | if (valueModel.label == "Other") { 128 | submitJsonValue.addProperty("value", valueModel.value) 129 | } else submitJsonValue.addProperty("value", aSwitch.text.toString()) 130 | } 131 | submitJsonValues.add(submitJsonValue) 132 | isChecked = true 133 | } 134 | } 135 | val json = populateSubmitPropertyJson(viewComponentModel, null) 136 | json.add("value", submitJsonValues) 137 | submitPropertyArrayJson?.add(json) 138 | return isChecked 139 | } else { 140 | for (i in 0 until (viewComponentModel.values?.size ?: 0)) { 141 | val submitJsonValue = JsonObject() 142 | val aSwitch = checkBoxContainer.getChildAt(i) as SwitchCompat 143 | valueModel = viewComponentModel.values?.get(i) 144 | if (aSwitch.isChecked) { 145 | submitJsonValue.addProperty("label", aSwitch.text.toString()) 146 | //submitJsonValue.addProperty("value", valueModel.getValue()); 147 | if (valueModel != null) { 148 | if (valueModel.label == "Other") { 149 | submitJsonValue.addProperty("value", valueModel.value) 150 | } else submitJsonValue.addProperty("value", aSwitch.text.toString()) 151 | } 152 | submitJsonValues.add(submitJsonValue) 153 | } 154 | } 155 | val json = populateSubmitPropertyJson(viewComponentModel, null) 156 | json.add("value", submitJsonValues) 157 | submitPropertyArrayJson?.add(json) 158 | true 159 | } 160 | } 161 | 162 | /** 163 | * @param view 164 | * @param FormComponentItem 165 | * @return Boolean value to check required field fill up or not 166 | */ 167 | fun getDataFromEditText( 168 | view: View, 169 | viewComponentModel: FormComponentItem 170 | ): Boolean { 171 | val editText = view as EditText 172 | if (editText.text.toString() != "") { 173 | viewComponentModel.required?.let { 174 | //Not null 175 | if (it) { 176 | if (editText.text.toString() == "") { 177 | return false 178 | } else { 179 | viewComponentModel.subtype?.let { subType -> 180 | if (subType == "tel") { 181 | if (!isValidTelephoneNumber(editText.text.toString())) { 182 | return false 183 | } 184 | } else if (subType == "email") { 185 | if (!isValidEmailAddress(editText.text.toString())) { 186 | return false 187 | } 188 | } 189 | } 190 | } 191 | } 192 | } 193 | val json = populateSubmitPropertyJson(viewComponentModel, null) 194 | json.addProperty("value", editText.text.toString()) 195 | submitPropertyArrayJson?.add(json) 196 | } 197 | return true 198 | } 199 | 200 | /** 201 | * @param view 202 | * @param FormComponentItem 203 | * @return 204 | */ 205 | fun getDataFromRadioGroup( 206 | view: View, 207 | viewComponentModel: FormComponentItem 208 | ): Boolean { 209 | val radioGroup = view as RadioGroup 210 | val valuesModels: ArrayList = viewComponentModel.values as ArrayList 211 | val valueModel: Value 212 | val radioButton: RadioButton? 213 | val radioButtonIndex: Int 214 | return if (viewComponentModel.required != null && viewComponentModel.required) { 215 | if (radioGroup.checkedRadioButtonId != -1) { 216 | radioButton = 217 | radioGroup.findViewById(radioGroup.checkedRadioButtonId) as RadioButton 218 | radioButtonIndex = radioGroup.indexOfChild(radioButton) 219 | valueModel = valuesModels[radioButtonIndex] 220 | val json = populateSubmitPropertyJson(viewComponentModel, valueModel) 221 | submitPropertyArrayJson?.add(json) 222 | Log.i("selectedRadioButton", valueModel.label) 223 | true 224 | } else { 225 | false 226 | } 227 | } else { 228 | if (radioGroup.checkedRadioButtonId != -1) { 229 | radioButton = 230 | radioGroup.findViewById(radioGroup.checkedRadioButtonId) as? RadioButton 231 | radioButton?.let { 232 | radioButtonIndex = radioGroup.indexOfChild(it) 233 | valueModel = valuesModels[radioButtonIndex] 234 | val json = populateSubmitPropertyJson(viewComponentModel, valueModel) 235 | submitPropertyArrayJson?.add(json) 236 | Log.i("selectedRadioButton", valueModel.label) 237 | } 238 | } 239 | true 240 | } 241 | } 242 | 243 | /** 244 | * @param view 245 | * @param FormComponentItem 246 | * @return 247 | */ 248 | fun getDataFromSpinner( 249 | view: View, 250 | viewComponentModel: FormComponentItem 251 | ): Boolean { 252 | val spinner = view as Spinner 253 | 254 | if (viewComponentModel.required != null && viewComponentModel.required) { 255 | return if (spinner.selectedItem != null) { 256 | val json = populateSubmitPropertyJson(viewComponentModel, null) 257 | val selectedValues = populateSpinnerOptions(viewComponentModel, spinner) 258 | json.add("value", selectedValues) 259 | submitPropertyArrayJson?.add(json) 260 | Log.i("SpinnerSelectedItem", spinner.selectedItem.toString()) 261 | true 262 | } else { 263 | false 264 | } 265 | } else { 266 | if (spinner.selectedItem != null) { 267 | val json = populateSubmitPropertyJson(viewComponentModel, null) 268 | val selectedValues = populateSpinnerOptions(viewComponentModel, spinner) 269 | json.add("value", selectedValues) 270 | submitPropertyArrayJson?.add(json) 271 | Log.i("SpinnerSelectedItem", spinner.selectedItem.toString()) 272 | return true 273 | } 274 | } 275 | 276 | return true 277 | } 278 | 279 | fun populateSpinnerOptions(componentItem: FormComponentItem, spinner: Spinner): JsonArray { 280 | val selectedValue = JsonObject() 281 | val selectedValues = JsonArray() 282 | selectedValue.addProperty("label", spinner.selectedItem.toString()) 283 | if (spinner.selectedItem.toString() == "Other") { 284 | selectedValue.addProperty( 285 | "value", 286 | componentItem.values 287 | ?.get(spinner.selectedItemPosition)?.value 288 | ) 289 | } else { 290 | selectedValue.addProperty( 291 | "value", 292 | spinner.selectedItem.toString() 293 | ) 294 | } 295 | selectedValues.add(selectedValue) 296 | return selectedValues 297 | } 298 | 299 | fun populateSubmitPropertyJson( 300 | componentItem: FormComponentItem, 301 | valueModel: Value? 302 | ): JsonObject { 303 | val submitPropertiesValueObj = JsonObject() 304 | submitPropertiesValueObj.addProperty("label", componentItem.label) 305 | valueModel?.let { 306 | if (it.label == "Other") { 307 | submitPropertiesValueObj.addProperty("value", it.value) 308 | } else submitPropertiesValueObj.addProperty("value", it.label) 309 | } 310 | submitPropertiesValueObj.addProperty("type", componentItem.type) 311 | submitPropertiesValueObj.addProperty("subtype", componentItem.subtype) 312 | return submitPropertiesValueObj 313 | } 314 | 315 | fun printProperties(array: JsonArray): String { 316 | val builder: StringBuilder = StringBuilder() 317 | for (i in 0 until array.size()) { 318 | val propObj = array[i].asJsonObject 319 | val label = propObj.get("label") 320 | val value = propObj.get("value") 321 | builder.append(label).append(" : ").append(value) 322 | builder.append("\n") 323 | } 324 | return builder.toString() 325 | } 326 | } 327 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/model/FormComponent.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson.model 2 | 3 | class FormComponent : ArrayList() -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/model/FormComponentItem.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson.model 2 | 3 | data class FormComponentItem( 4 | val access: Boolean, 5 | val className: String, 6 | val `inline`: Boolean, 7 | val label: String?, 8 | val max: Long?, 9 | val min: Int?, 10 | val step: Int?, 11 | val multiple: Boolean?, 12 | val name: String?, 13 | val value: String?, 14 | val maxlength: String?, 15 | val rows: String?, 16 | val other: Boolean?, 17 | val placeholder: String?, 18 | val required: Boolean?, 19 | val style: String?, 20 | val subtype: String?, 21 | val toggle: Boolean?, 22 | val type: String?, 23 | val values: List? 24 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/model/FormViewComponent.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson.model 2 | 3 | import android.view.View 4 | 5 | class FormViewComponent( 6 | val createdView: View, 7 | viewComponentModel: FormComponentItem 8 | ) { 9 | private val viewComponentModel: FormComponentItem = viewComponentModel 10 | fun getViewComponentModel(): FormComponentItem { 11 | return viewComponentModel 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/model/Value.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson.model 2 | 3 | data class Value( 4 | val label: String, 5 | val selected: Boolean?, 6 | var value: String? 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/model/WidgetItems.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson.model 2 | 3 | enum class WidgetItems(val label: String) { 4 | HEADER("header"), 5 | TEXT("text"), 6 | TEXTAREA("textarea"), 7 | SELECT("select"), 8 | RADIO_GROUP("radio-group"), 9 | PARAGRAPH("paragraph"), 10 | DATE("date"), 11 | CHECKBOX_GROUP("checkbox-group"), 12 | NUMBER("number") 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson.utils 2 | 3 | import android.content.Context 4 | import android.content.res.ColorStateList 5 | import android.graphics.Color 6 | import android.os.Build 7 | import android.text.Html 8 | import android.util.Log 9 | import android.view.View 10 | import android.widget.LinearLayout 11 | import androidx.appcompat.widget.SwitchCompat 12 | import androidx.core.content.ContextCompat 13 | import androidx.core.graphics.drawable.DrawableCompat 14 | import com.rafsan.dynamicui_fromjson.R 15 | import java.text.ParseException 16 | import java.text.SimpleDateFormat 17 | import java.util.* 18 | import java.util.regex.Pattern 19 | 20 | class Utils { 21 | companion object { 22 | fun fromHtml(str: String): String { 23 | return if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(str, Html.FROM_HTML_MODE_LEGACY) 24 | .toString() else Html.fromHtml(str).toString() 25 | } 26 | 27 | fun setMerginToviews(view: View, topMergin: Int, width: Int, height: Int) { 28 | val layoutParams = LinearLayout.LayoutParams(width, height) 29 | layoutParams.setMargins(40, topMergin, 40, 0) 30 | view.layoutParams = layoutParams 31 | } 32 | 33 | fun setMerginToviews(view: View) { 34 | val layoutParams = LinearLayout.LayoutParams( 35 | LinearLayout.LayoutParams.WRAP_CONTENT, 36 | LinearLayout.LayoutParams.WRAP_CONTENT 37 | ) 38 | layoutParams.setMargins(40, 20, 40, 0) 39 | view.layoutParams = layoutParams 40 | } 41 | 42 | fun method(str: String?): String? { 43 | var str = str 44 | if (str != null && str.isNotEmpty()) { 45 | str = str.substring(0, str.length - 1) 46 | } 47 | return str 48 | } 49 | 50 | fun getCustomColorStateList(context: Context): ColorStateList { 51 | return ColorStateList( 52 | arrayOf( 53 | intArrayOf(android.R.attr.state_checked), 54 | intArrayOf(-android.R.attr.state_checked) 55 | ), 56 | intArrayOf( 57 | ContextCompat.getColor(context, R.color.grey),//disabled 58 | ContextCompat.getColor(context, R.color.teal_500) //enabled 59 | ) 60 | ) 61 | } 62 | 63 | fun getCurrentDate(): Date { 64 | val calendar = Calendar.getInstance() 65 | return calendar.time 66 | } 67 | 68 | fun getDateStringToShow(date: Date, format: String): String { 69 | try { 70 | val simpleDateFormat = SimpleDateFormat(format, Locale.ENGLISH); 71 | return simpleDateFormat.format(date) 72 | } catch (e: Exception) { 73 | Log.d("Error", e.toString()) 74 | return "" 75 | } 76 | } 77 | 78 | fun getDateFromString(date: String, format: String): Date? { 79 | Log.i("StringDate", date) 80 | val simpleDateFormat = SimpleDateFormat(format, Locale.ENGLISH) 81 | try { 82 | val formatedDate = simpleDateFormat.parse(date) 83 | Log.i("ParsedDate", formatedDate.toString()) 84 | return formatedDate 85 | } catch (e: ParseException) { 86 | e.printStackTrace() 87 | } 88 | return null 89 | } 90 | 91 | fun setSwitchColor(v: SwitchCompat, context: Context) { 92 | // thumb color of your choice 93 | val thumbColor = ContextCompat.getColor(context, R.color.teal_500) 94 | 95 | // trackColor is the thumbColor with 30% transparency (77) 96 | val trackColor = Color.argb( 97 | 77, Color.red(thumbColor), 98 | Color.green(thumbColor), 99 | Color.blue(thumbColor) 100 | ) 101 | 102 | // setting the thumb color 103 | DrawableCompat.setTintList( 104 | v.thumbDrawable, ColorStateList( 105 | arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()), intArrayOf( 106 | thumbColor, 107 | Color.WHITE 108 | ) 109 | ) 110 | ) 111 | 112 | // setting the track color 113 | DrawableCompat.setTintList( 114 | v.trackDrawable, ColorStateList( 115 | arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()), intArrayOf( 116 | trackColor, 117 | Color.parseColor("#4D000000") // full black with 30% transparency (4D) 118 | ) 119 | ) 120 | ) 121 | } 122 | 123 | fun isValidEmailAddress(emailAddress: String): Boolean { 124 | val emailPattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" 125 | val p = Pattern.compile(emailPattern) 126 | val m = p.matcher(emailAddress) 127 | return m.matches() 128 | } 129 | 130 | fun isValidTelephoneNumber(telephoneNumber: String): Boolean { 131 | return telephoneNumber.length >= 10 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/utils/dialog/DialogListener.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson.utils.dialog 2 | 3 | interface DialogListener { 4 | fun OnSuccess(obj: Any?) 5 | fun OnError(obj: Any?) 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/rafsan/dynamicui_fromjson/utils/dialog/ShowDialog.kt: -------------------------------------------------------------------------------- 1 | package com.rafsan.dynamicui_fromjson.utils.dialog 2 | 3 | import android.app.Activity 4 | import android.app.Dialog 5 | import android.content.Context 6 | import android.view.Gravity 7 | import android.view.LayoutInflater 8 | import android.view.Window 9 | import android.view.WindowManager 10 | import com.rafsan.dynamicui_fromjson.databinding.DialogLayoutBinding 11 | 12 | class ShowDialog { 13 | companion object { 14 | fun customDialog( 15 | context: Context, 16 | title: String, 17 | message: String, 18 | listener: DialogListener? 19 | ) { 20 | val dialog = Dialog(context) 21 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) 22 | val inflater = 23 | context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater 24 | val binding: DialogLayoutBinding = DialogLayoutBinding.inflate(inflater) 25 | binding.modalTitle.text = title 26 | binding.modalMsg.text = message 27 | binding.modalConfirm.setOnClickListener { 28 | dialog.dismiss() 29 | listener?.OnSuccess("") 30 | } 31 | (context as Activity).window.setSoftInputMode( 32 | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE or 33 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE 34 | ) 35 | dialog.setContentView(binding.root) 36 | val window = dialog.window 37 | window!!.setLayout( 38 | WindowManager.LayoutParams.MATCH_PARENT, 39 | WindowManager.LayoutParams.WRAP_CONTENT 40 | ) 41 | window.setGravity(Gravity.CENTER) 42 | dialog.show() 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /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/edit_text_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /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/layout/activity_generate_form.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 21 | 22 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 |