As I've promised - here is my experience with two ...
# splitties
i
As I've promised - here is my experience with two cool libs: Splitties DSL and Conductor. They combine pretty good with each other, and deliver strong feeling that this approach should be standard for Android development. Here are main benefits: 1. Single activity, no fragments, zero lifecycle management. 2. Cool direct navigation between Controllers without activity listeners and all that stuff. 3. Cool and fast UI generation without xml. 4. Very simple implementation of MVP - imo best approach in case of generated UI. Here is some sample code I've extracted and refactored for simplicity (one piece of code is better than thousand words :))
Copy code
class ItemsUi(override val ctx: Context,
              private val displayHelper: DisplayHelper) // Any other stuff for abstraction from platform...
    : Ui {

    /**
     * Holder for buttons.
     */
    val content = verticalLayout {
    }

    /**
     * Root of Ui. Should be below all other properties for correct initialization order.
     */
    override val root = verticalLayout {
        background = resources.getDrawable(android.R.color.white, null)

        add(content, lParams {
            width = matchParent
            height = matchParent
        })
    }

    /**
     * Creates UI based on data.
     */
    fun createUi(items: List<Item>?) {
        content.apply {
            for (item in items) {
                add(createButton(item), lParams {
                    width = matchParent
                    height = matchParent
                })
            }
        }
    }
}

class ItemsPresenter(ownerScope: CoroutineScope,
                     private var view: View?,
                     private val loadItemsCommand: LoadItemsCommand) : CoroutineScope by ownerScope {
    interface View {
        fun showItems(items: List<Item>?)
    }

    fun loadItems() = launch(<http://Dispatchers.IO|Dispatchers.IO>) {
        val items = loadItemsCommand(null)

        withContext(Dispatchers.Main) {
            view?.showItems(items)
        }
    }

    fun stop() {
        view = null
    }
}

class ItemsController : Controller(),
        CoroutineScope,
        KoinComponent,
        ItemsPresenter.View {

    private val itemsPresenter: ItemsPresenter by inject { parametersOf(this, this) }
    private lateinit var itemsUi: ItemsUi

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
        itemsPresenter.loadItems()

        val displayHelper: DisplayHelper by inject()
        itemsUi = ItemsUi(container.context, displayHelper, ::onItemSelection)
        // or, alternatively, you can do something like that:
        // itemsUi.button.onClick {
        //            val pushHandler = FadeChangeHandler(false)
        //            val popHandler = FadeChangeHandler()
        //
        //            router.pushController(RouterTransaction.with(ItemDetailsController())
        //                    .pushChangeHandler(pushHandler)
        //                    .popChangeHandler(popHandler))
        //        }
        return itemsUi.root
    }

    override fun onDestroyView(view: View) {
        super.onDestroyView(view)
        itemsPresenter.stop()
    }

    override fun showItems(items: List<Item>?) {
        itemsUi.createUi(items)
    }

    private fun onItemSelection(itemId: Int) {
        val pushHandler = FadeChangeHandler(false)
        val popHandler = FadeChangeHandler()

        router.pushController(RouterTransaction.with(ItemDetailsController())
                .pushChangeHandler(pushHandler)
                .popChangeHandler(popHandler))
    }

    override val coroutineContext: CoroutineContext by lazy {
        (activity as MainActivity).coroutineContext
    }

}
πŸ‘ 1
One thing to be noted here - you should be really careful with id for root
Controller
container. Don't use
View.generateViewId()
, and just assign any int for it as described here (https://stackoverflow.com/questions/8460680/how-can-i-assign-an-id-to-a-view-programmatically). Id should be constant, otherwise you will get invalid backstack behavior on configuration changes. BTW, is there any helper class for that is Splitties?
@louiscad How do you deal with static view ids in your projects? Just specify them as class variables?
Copy code
private val rootId = 666001 // Constant root id.
    private val buttonId = 666002 // Constant button id.
Do you retain view state this way, or save/restore it by exact variables in Ui subclasses?
l
@ispbox I use xml defined ids. The IDE even has an intention to create them from code
R.id.not_existing_yet
.
Those are not needed for views that don't keep user input though.
A plain
Button
is stateless and doesn't need it for example. Same for a
TextView
. On the other hand, you definitely want stable ids for
EditText
,
ScrollView
,
RecyclerView
,
SeekBar
,
SwitchCompat
, etc.
i
Thank you very much! Looks very promising. Probably it's better than reinvent the wheel. For styling, ids and maybe some other things xmls are still valid πŸ™‚
l
Well, having the wheel reinvented will probably prove better in the future with #compose πŸ˜…
Actually, if they don't want to redesign all activity/fragment stuff, I see no reason to switch to Compose from Conductor + Splitties πŸ™‚ And as far as I understand, Compose is full of @-like annotations? That's terrible!
l
Compose is more akin to coroutines but for trees instead of long running/waiting code. I thought the same as you at first, but going beyond that, it seems promising, and I can't wait to try it with coroutines!
Do you wish Conductor to have a more Kotlin-y API, or is it great to use for you in its current state? Maybe you already developed extensions for it?
i
@louiscad, do you mean that compose will generate UI in background? Is that possible? But actually I don't see this as a main problem for android development πŸ™‚ As for your second question, I'm really excited with Conductor, and wish I had known it earlier. It is written in Java, but that is not a problem at all. It is so pretty simple and really awesome in functionality! I see no reason to use any available extensions (not in Kotlin as well ;), it works pretty good with MVP pattern without any Mosby or other stuff.
I'm a big Kotlin fan, and I like coroutines, but actually for now I see no reason to enhance Conductor or Kotlinize it.
l
I mean a
@Composable
function requires to be called from another one, much like
suspend
functions. Regarding building part or all of the UI on a background thread, they are considering it and have to test if that has good outcomes in practice.
i
I treat all that extra annotations as Java-world old-fashioned trash πŸ™‚ Actually using Kotlin there are not so many use-cases for annotations. That's why I prefer Koin over Dagger, or Spek over JUnit, for example. I'm more or less OK with annotations in Firebase Performance (it is really convenient), but elegancy of composing UI in Splitties or Anko is better for me πŸ™‚
And see no problem of calling one UI-generation function from another one (or property, like you prefer in Splitties) without that
Composable
stuff.
Here is a pretty good sample how Compose tries to reinvent the wheel. Anko solved that long ago, and it just works. https://kotlinlang.slack.com/archives/CJLTWPH7S/p1565158290118600
l
Compose is looking for just letting us have conditions (`if`/`else`, `when`…). Don't get fooled by it's annotation processor look, the
suspend
keyword is actually an annotation for the compiler, just like
@Compose
is. They thought about making it a keyword (
compose fun
), but they're not certain it's the right thing to add a keyword. An annotation is more library space and doesn't conflict with others in the project unless there's a fundamental clash.
But Compose is well into development for now, and I'll have to make a new UI for next release at work before it's done. It'll use Splitties (will actually ditch some old xml).
i
Compose is looking for just letting us have conditions
And what's the problem now? πŸ™‚ Kotlin already has this 'magic' πŸ™‚
Compose is well into development for now, and I'll have to make a new UI for next release at work before it's done.
You can use Flutter. I see no reason to be so excited with Compose... It doesn't serve coffee at least πŸ™‚
l
Compose is in Kotlin, that's why it'll work in Compose with nothing added from them. It's like suspending functions, all language features work with them.
Flutter is Dart…
No extension functions, no coroutines (only syntactic sugar for futures called async/await that doesn't support structured concurrency and cancellation)…
And no Java and Obj-C interop like Kotlin has
i
Compose is in Kotlin, that's why it'll work in Compose with nothing added from them.
And the same applies to Splitties. Why are you so pessimistic? You already have a library, and Google has to work for maybe 1 or 2 years to get that in production.
l
I'm not pessimistic, I'm enthusiastic! Splitties is not going away, it's close to the best we can get with
android.view.View
IMO. However, despite that, there's a need for a lot of fine tuning, and many things are very complicated. The current state makes me want to be a backend mobile developer only (not deal with how the app will actually look). Compose promises to make this dramatically easier, so I can only hope for it to come.
i
Actually, with Conductor and Splitties my life became much, much better! πŸ™‚ Beautiful animations are included in Conductor, while Splitties provide cool UI generation. Maybe my UI is not so complicated, but I see no real obstacles to solve with Compose πŸ™‚ What I would really like to have from Compose is support for multiplatform Kotlin development. But I don't believe in success with that in the near future 😞
l
Compose will allow to make things that are not even UI, so you can be sure to see an iOS implementation of Compose UI at some point, and for other Kotlin targets too, including JVM. Regarding multiplatform, I've thought about porting SnapKit or an equivalent in Kotlin/Native, so I can then use coroutines in full Kotlin UI. Can you confirm Conductor allows you to keep using
DialogFragment
without issues?
i
To make multi-platform UI really valuable you have to make its API 100% equal on all supported platforms. Currently afaik only js-based frameworks give that opportunity, all others have more or less differences. As for me, it would be cool to have Splitties for iOS backed by Kotlin Native πŸ™‚ But I guess there is a very long road for that (and for Compose as well). As for Conductor - yes, they have DialogController (not Fragment), and it is not only fully-functional - it also supports cool animation for showing and hiding πŸ™‚ Just take a look at their sample app.
l
100% is not needed. It's only a dream that is impossible if you use UIKit and Android Views