icerockdev / moko-widgets

Multiplatform UI DSL with screen management in common code for mobile (android & ios) Kotlin Multiplatform development
https://moko.icerock.dev
Apache License 2.0
388 stars 32 forks source link

Add support of runtime Id creation #199

Closed Gamadril closed 4 years ago

Gamadril commented 4 years ago

I updated my project based on the recent commit of moko-widgets-template, so using 0.1.0-dev-18 now. And my code previously working with -dev12 throws an exception on Android run now.

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: org.example.app.debug, PID: 18128
    dev.icerock.moko.widgets.utils.AndroidIdConflictException: id 0x7F081AB9 already used by org.example.mpp.MainScreen$createInput$1$2, it conflict with org.example.mpp.MainScreen$createInput$1$2
        at dev.icerock.moko.widgets.utils.IdExtKt.getAndroidId(IdExt.kt:45)
...

I stripped down the test case to minimum for reproducing the error:

package org.example.mpp

import dev.icerock.moko.fields.FormField import dev.icerock.moko.fields.liveBlock import dev.icerock.moko.resources.desc.StringDesc import dev.icerock.moko.resources.desc.desc import dev.icerock.moko.widgets.* import dev.icerock.moko.widgets.core.Theme import dev.icerock.moko.widgets.core.Widget import dev.icerock.moko.widgets.screen.Args import dev.icerock.moko.widgets.screen.WidgetScreen import dev.icerock.moko.widgets.style.view.SizeSpec import dev.icerock.moko.widgets.style.view.WidgetSize import org.example.library.MR

class MainScreen( private val theme: Theme ) : WidgetScreen() {

private fun createInput() : Widget<*>{
    return with(theme) {
        input(
            size = WidgetSize.WrapContent,
            label = const(MR.strings.hello_world.desc() as StringDesc),
            field = FormField(initialValue = "", validation = liveBlock { null }),
            id = object : InputWidget.Id {}
        )
    }
}

override fun createContentWidget() = with(theme) {
    constraint(size = WidgetSize.AsParent) {
        val scroll = +scroll(
            size = WidgetSize.Const(SizeSpec.MatchConstraint, SizeSpec.MatchConstraint),
            id = object : ScrollWidget.Id {},
            child = linear(
                size = WidgetSize.Const(SizeSpec.AsParent, SizeSpec.WrapContent)
            ) {
                +createInput()
                +createInput()
                +createInput()
            })

        constraints {
            scroll.topToTop(root.safeArea)
            scroll.leftRightToLeftRight(root.safeArea)
            scroll.bottomToBottom(root.safeArea)
        }
    }
}

}


- run in Android emulator

The input fields are generated dynamically, the id of the returned input field is a new object, but it seems that the moko-widgets lib thinks it's the same. Or is the code just wrong and it's not the right way to do it?
Tetraquark commented 4 years ago

To programmatically create Android views, we have to manually generate view IDs. The current generating algorithm uses java class name of a widget Id objects (you can see this here: https://github.com/icerockdev/moko-widgets/blob/master/widgets/src/androidMain/kotlin/dev/icerock/moko/widgets/utils/IdExt.kt#L15). This means that each widget Id object must have a unique class name. In your implementation, input widgets have different Id objects, but they have the same class name, so exception happens. So, in my opinion, your version of dynamic widget creation is not quite correct. One way to create a screen with the dynamic list of widgets is to use moko-units library with ListWidget. In other words, do not create a linear widget inside the scroll with dynamic content, but create a list of widgets using ListWidget and objects of theWidgetsTableUnitItem class as list items. You can find some example here: https://github.com/icerockdev/moko-widgets/blob/master/sample/mpp-library/src/commonMain/kotlin/dev/icerock/moko/widgets/sample/CollectionScreen.kt

Gamadril commented 4 years ago

Thanks for the clarification. However it seems moko-units is for displaying items inside the RecyclerView/UITableView/UICollectionView - so items that are of the same structure. My goal is to create different types of widgets inside a top - down container. They might have different styles, size and position. So let's say I have the description of the UI (JSON, XML or whatever else) from which I want to create the screens using moko-widgets. The ID in that case is always just a String.

Alex009 commented 4 years ago

i think we can add specific implementation of ID with string id, not class name...i mark issue as enhacement

Alex009 commented 4 years ago

runtime ids can be created with autogenerated functions like: inputWidgetId(uniqueId = "myUniqueId") buttonWidgetId(uniqueId = "myUniqueId")

Alex009 commented 4 years ago

@Gamadril you can test it now on dev repo - https://bintray.com/beta/#/icerockdev/moko-dev/moko-widgets/bd962b3?tab=overview