0

I have the following viewModel code in my Android Application

fun perform(operation: Action.Operation, sideEffects: (List<SideEffect>) -> Unit, render: (Reaction<String>) -> Unit): IO<Unit> = IO.fx {

    !effect(Dispatchers.Main) { sideEffects(operation.sideEffects) }
    val response = !effect(Dispatchers.IO) { repository.network(operation) }
    !effect(Dispatchers.Main) { sideEffects(response.sideEffects) }
    !effect(Dispatchers.Main) { render(response) }
}

where sideEffects and render functions resemble this from the calling Activity

private val sideEffects: (List<SideEffect>) -> Unit = { sideEffects ->
    sideEffects.forEach { sideEffect ->
        Timber.e("$sideEffect")
        val exhaustive = when (sideEffect) {
            is SideEffect.UiSideEffect.Inactive -> {
                mProgressBar.visibility = View.GONE
            }
            is SideEffect.UiSideEffect.Active -> {
                mProgressBar.visibility = View.VISIBLE
            }
            is SideEffect.UiSideEffect.Enable -> Timber.d("SIDE EFFECT:: TODO() $sideEffect")
            is SideEffect.UiSideEffect.Disable -> Timber.d("SIDE EFFECT:: TODO() $sideEffect")
            is SideEffect.Logging.Log -> Timber.d("SIDE EFFECT:: {logging} ${sideEffect.message}")
            is SideEffect.Analytical.Analytics -> Timber.d("SIDE EFFECT:: TODO() $sideEffect")
        }
    }
}

My activity calls the perform method from its onCreate() method

private lateinit var viewModel: MainViewModel

private lateinit var textView: TextView
private lateinit var mProgressBar: ProgressBar

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    textView = findViewById(R.id.hello_world_tv)
    mProgressBar = findViewById(R.id.progress_bar_cyclic)

    viewModel = ViewModelProviders.of(this)[MainViewModel::class.java]

    val cancel = viewModel.perform(Action.Operation(), sideEffects, render).unsafeRunAsyncCancellable { either ->
        when (either) {
            is Either.Left -> {
                Timber.e("oh Dear!!!! ${either.a}")
                //Show Error!!!
            }

            is Either.Right -> {
                Timber.e("Everything Worked correctly ${either.b}")
            }
        }
    }
}

Is this a safe approach to take?

does it matter I am passing references to my TextView and ProgressBar into my viewModel?

Hector
  • 4,016
  • 21
  • 112
  • 211
  • 1
    The concept of "leaking UI" from your view to the viewmodel is a problem when the viewmodel holds onto the reference. What you're *really* in your code doing is holding viewmodel stuff in your view. Your `perform` function isn't actually performing anything. It's actually just creating an IO object and giving that to your view (Activity/Fragment). So `perform` is giving references to your repository, really. Your viewmodel isn't holding onto anything related to your UI. There isn't any leaking from UI to viewmodel. – user1713450 Dec 10 '19 at 17:58
  • 1
    That being said, I would move the creation of your viewmodel out of onCreate, since creation of the vm doesn't require any context. Make it a private class variable. And while I said what you're doing isn't risking any leaks, I think stylistically it makes the code confusing. Why is your viewmodel concerned with the view at all? If you're using viewmodel, presumably you're looking at MVVM organization, so your view should either be telling the vm "get some data from network" and then viewmodel says back "ok here's the data" and then view concerns itself with the side effects of changing the UI – user1713450 Dec 10 '19 at 18:05
  • @user1713450 thanks for looking at my question. I'm actually aiming at MVI, however the above code is a first try – Hector Dec 10 '19 at 19:00
  • 1
    Well when you get around to that and want to share, @ me or something and if I see it I'll respond. I do my Android work in MVI (without Rx at all). Right now I'm trying to learn Arrow and make my MVI more functional so my unit tests can be rock solid. I'd run into issues where I wasn't injecting dispatchers into my VMs so I was doing a lot of viewModelScope.launch(Dispatchers.IO) inside my "perform" when interacting with the DB and had no way of making my test wait to do its asserts until after the coroutine launched within "perform" was done. – user1713450 Dec 10 '19 at 20:40
  • 1
    And rather than refactor to inject dispatchers so my unit tests could know when the coroutines launched from "perform" were finished, I decided it was a good time to try and make my "perform" return IO. I also decided to consider viewmodel.state updates not an "effect" for personal reasons. Thinking less about purity of a function and more about purity of my viewmodel class. So my `perform` actually returns Option. Yadda yada, I'm boring you. Anyhoo, my MVI use is pretty rock solid if you ignore the Arrow stuff. – user1713450 Dec 10 '19 at 20:44
  • @user1713450 I'm intending to employ Koin for DI, its a BIG improvement on Dagger. For FP to work for MVI I believe perform() has to be polymorphic and I need a mechanism that supports screen rotation while perform() is "in-flight". I will let you know how I get on – Hector Dec 11 '19 at 10:00
  • 1
    I wrote an article that covered this topic: https://www.pacoworks.com/2019/12/15/kotlin-coroutines-with-arrow-fx/ Check the section on "Program structure in fx" – El Paco Dec 29 '19 at 20:21

0 Answers0