Last active
June 24, 2020 20:27
-
-
Save thegarlynch/476ba1474a2ad96ff28872dd7e9d70ef to your computer and use it in GitHub Desktop.
Dynamic Form
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.view.View | |
import android.widget.LinearLayout | |
import io.reactivex.Observable | |
interface Input { | |
val mandatory : Boolean | |
val focusIds : Array<Int> | |
fun buildView(context : Context) : View | |
val completeSignal : Observable<Boolean> | |
val layoutParams : LinearLayout.LayoutParams | |
} | |
class InputForeman(private val schema : List<Input>) { | |
fun build(context: Context) : LinearLayout { | |
TODO("buildView into vertical LinearLayout based on it's layoutParams") | |
} | |
fun observeCompletion() : Observable<Boolean> { | |
TODO("join all schema completeSignal") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.view.View | |
import android.widget.LinearLayout | |
import io.reactivex.Observable | |
interface Input { | |
val mandatory : Boolean | |
fun buildView(context : Context) : View | |
val completeSignal : Observable<Boolean> | |
val layoutParams : LinearLayout.LayoutParams | |
} | |
/** | |
* Then I realize that every [Input] needs id so you can set value to it | |
*/ | |
typealias ID = String | |
class InputForeman(private val schema : Map<ID, Input>) { | |
private lateinit var inputViews : Map<ID, View> | |
fun build(context: Context) : LinearLayout { | |
TODO("buildView into vertical LinearLayout based on it's layoutParams") | |
} | |
fun observeCompletion() : Observable<Boolean> { | |
TODO("join all input completeSignal") | |
} | |
fun getValue() : Map<ID, Any?> { | |
TODO("") | |
} | |
fun setValue(values : Map<ID, Any?>){ | |
TODO("") | |
} | |
} | |
/** | |
* | |
* But this is still bad since it turns out, every implementation of [Input] i made, also made schema holds [View] instance unintentionally | |
* | |
* @sample | |
* | |
* class EditInput(override mandatory : Boolean) : Input { | |
* | |
* private lateinit var editText : EditText | |
* | |
* override fun buildView(context : Context) : EditText { | |
* editText = EditText(context) | |
* return editText | |
* } | |
* | |
* override val completeSignal : Observable<Boolean> | |
* get() { | |
* return Observable.create { emit -> | |
* if(mandatory){ | |
* it.onNext(true) | |
* editText.addOnTextChanged { | |
* emit.onNext(it.toString().isNotEmpty()) | |
* } | |
* }else{ | |
* it.onNext(true) | |
* } | |
* } | |
* } | |
* | |
* override val layoutParams = LinearLayout.LayoutParams(-1, -2) | |
* | |
* } | |
* | |
* So, i can't do this, [Input] is better if it is immutable, so that i can save schema to storage | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.view.View | |
import android.widget.LinearLayout | |
import io.reactivex.Observable | |
interface Input { | |
val mandatory : Boolean | |
fun buildView(context : Context) : InputHolder | |
val layoutParams : LinearLayout.LayoutParams | |
} | |
/** | |
* Now this is better | |
*/ | |
class InputHolder( | |
val view : View, | |
val completeSignal : Observable<Boolean>, | |
/** | |
* imagine if i have Composite Input where it buildView based on it's child input | |
* and imagine if this Composite Input somehow compose all EditText in horizontal manner | |
* | |
* | |
* Turns out i need view focusesId too. you can use [View.generateViewId] when you build the view | |
*/ | |
val focusedIds : Array<Int> | |
) | |
typealias ID = String | |
class InputForeman(private val schema : Map<ID, Input>) { | |
private lateinit var inputHolders : Map<ID, InputHolder> | |
fun build(context: Context) : LinearLayout { | |
TODO("buildView into vertical LinearLayout based on it's layoutParams") | |
} | |
fun observeCompletion() : Observable<Boolean> { | |
TODO("join all input completeSignal") | |
} | |
/** | |
* Turns out by making value [Any] is something you should not do. | |
* since you will lose the benefit of knowing what type the input value should be cast with | |
*/ | |
fun getValue() : Map<ID, Any?> { | |
TODO("") | |
} | |
fun setValue(values : Map<ID, Any?>){ | |
TODO("") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.view.View | |
import android.widget.LinearLayout | |
import io.reactivex.Observable | |
/** | |
* i want to made Input<T> with generic T so i can enforce the value for InputHolder | |
* is it good ? hmm.. i dunno.. if you do this. you might have need class<T> TOO!! | |
* | |
* but let's try it.. | |
*/ | |
interface Input<T> { | |
val cls : Class<T> | |
val mandatory : Boolean | |
fun buildView(context : Context) : InputHolder<T> | |
val layoutParams : LinearLayout.LayoutParams | |
} | |
abstract class InputHolder<T>( | |
val view : View, | |
val completeSignal : Observable<Boolean>, | |
val focusedIds : Array<Int> | |
){ | |
abstract fun setValue(value : T) | |
abstract fun getValue() : T | |
} | |
/** | |
* But this is BAD TOO!! | |
* | |
* T will be an invariant!! | |
*/ | |
typealias ID = String | |
class InputForeman(private val schema : Map<ID, Input<*>>) { | |
private lateinit var inputHolders : Map<ID, InputHolder<*>> | |
fun build(context: Context) : LinearLayout { | |
TODO("buildView into vertical LinearLayout based on it's layoutParams") | |
} | |
fun observeCompletion() : Observable<Boolean> { | |
TODO("join all input completeSignal") | |
} | |
fun getValue() : Map<ID, Any?> { | |
TODO("") | |
} | |
fun setValue(values : Map<ID, Any?>){ | |
TODO("") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.view.View | |
import android.widget.LinearLayout | |
import io.reactivex.Observable | |
/** | |
* Enter my inspiration, JsonWriter and JsonReader | |
*/ | |
typealias ID = String | |
interface Input<T> { | |
/** | |
* But writer and reader needs ID!! | |
*/ | |
val id : ID | |
val cls : Class<T> | |
val mandatory : Boolean | |
fun buildView(context : Context) : InputHolder<T> | |
val layoutParams : LinearLayout.LayoutParams | |
} | |
class Writer(){ | |
private val innerMap = mutableMapOf<ID, Any?>() | |
val map : Map<ID, Any?> get() = innerMap | |
fun <T> write(input : Input<T>, value : T){ | |
innerMap[input.id] = input.cls.cast(value) | |
} | |
} | |
class Reader(private val map : Map<ID, Any?>){ | |
fun <T> read(input : Input<T>) : T?{ | |
val value = map.getValue(input.id) | |
return input.cls.cast(value) | |
} | |
} | |
abstract class InputHolder<T>( | |
/** | |
* Turns out you need input from input holder | |
*/ | |
val input : Input<T>, | |
val view : View, | |
val completeSignal : Observable<Boolean>, | |
val focusedIds : Array<Int> | |
){ | |
abstract fun write(writer: Writer) | |
abstract fun read(reader: Reader) | |
} | |
class InputForeman(private val schema : List<Input<*>>) { | |
private lateinit var inputHolders : Map<ID, InputHolder<*>> | |
fun build(context: Context) : LinearLayout { | |
TODO("buildView into vertical LinearLayout based on it's layoutParams") | |
} | |
fun observeCompletion() : Observable<Boolean> { | |
TODO("join all input completeSignal") | |
} | |
/** | |
* Turns out this is different than you think, | |
* [InputHolder.write] is used to get value from view | |
* and [InputHolder.read] is used to set value from Values Map | |
*/ | |
fun getValue() : Map<ID, Any?> { | |
val writer = Writer() | |
for((id, inputHolder) in inputHolders){ | |
inputHolder.write(writer) | |
} | |
return writer.map | |
} | |
fun setValue(values : Map<ID, Any?>){ | |
val reader = Reader(values) | |
for((id, inputHolder) in inputHolders){ | |
inputHolder.read(reader) | |
} | |
} | |
} | |
/** | |
* Another idea i have tried is making T a subclass of Value object to made it easier | |
* to be converted to moshi | |
* | |
* To make it more complicated sometimes, | |
* sometimes you need [LiveData] object so that you can generate for example Dropdown options on REAL TIME | |
* | |
* where do you put it ? | |
* i don't code how to solve this yet. but better watch out for this kind of thing | |
* | |
* Oh, some advice. don't use recyclerView in this. JUST DON'T | |
* I have thinking a lot about how to abstract this into RecyclerView | |
* but it is too hard. you have to consider binding. ViewType. | |
* | |
* if you think about it, why do you need recyclerview at all ? | |
* the only benefit you get from recyclerView is reducing view. | |
* | |
* but all the cases that i found. you don't always get 10 views with the same input | |
* it is always at most 6 to 8 EditTextInput, 2 or 3 ImageInput. your ViewHolder isn't gonna save you from | |
* input variation | |
* | |
* that's the end of our journey. i hope this is helpful | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment