0

How i can move logic from fragment in my ViewModel?

override fun onItemClick(titleName: Int) {
    when (titleName) {
        R.string.about_terms_service -> {
            activity?.addFragment(
                WebViewFragment.newInstance(
                    TERMS_LINK,
                    getString(R.string.about_terms_service)
                )
            )
        }
        R.string.about_open_source_licenses -> activity?.addFragment(LicensesFragment())
    }
}
Morozov
  • 4,968
  • 6
  • 39
  • 70

2 Answers2

1

This logic can be moved to ViewModel, with a bit of overhead though. For sure, the decision of which fragment to add should be moved to ViewModel, but the adding fragment's code should stay in Fragment (or Activity). I think it should look something like this:

Fragment:

override fun onItemClick(titleName: Int) {
    viewModel.onTitleClick(titleName)
}

ViewModel:

fun onTitleClick(titleName: Int) {
    when (titleName) {
        R.string.about_terms_service -> {
            postViewModelEvent(ShowWebViewFragmentEvent())
        }
        R.string.about_open_source_licenses -> {
            // TODO: open License fragment
        }
    }
}

In the ViewModel you should replace this // TODOs with a specific command to your View (Fragment, Activity), which will trigger navigating to specific fragment. On how to do that, for example, is written here (but of course, any ViewModel - Fragment solution will work).

In this case, this logic can be easily tested.

If you're connecting your ViewModel with your Fragment via view model events (described in the link), you can do this:

Create an event of showing WebViewFragment like this:

class ShowWebViewFragmentEvent(): ViewModelEvent {
    override fun handle(activity: BaseActivity) {
        super.handle(activity)
        activity?.addFragment(
                WebViewFragment.newInstance(
                    TERMS_LINK,
                    getString(R.string.about_terms_service)
                )
            )
    }
}

and post it in your ViewModel (replacing first // TODO) like this:

postViewModelEvent(ShowWebViewFragmentEvent())

Please note, that all the required changes from the linked post should be done.

Morozov
  • 4,968
  • 6
  • 39
  • 70
Demigod
  • 5,073
  • 3
  • 31
  • 49
  • hm, and for example i need to create interface how i created in mvp. – Morozov Mar 30 '20 at 12:44
  • i use code for my fragment which i see here https://stackoverflow.com/questions/46429511/android-viewmodel-call-activity-methods/58076044#58076044 , but also can't understant what i need to write in my // TODO: – Morozov Mar 30 '20 at 15:31
  • If you're using code which observes ViewModel events in your Fragment, then you should post/emit your new/specific events in `// TODO` – Demigod Mar 30 '20 at 15:38
  • can u give please one example for first condition? – Morozov Mar 30 '20 at 15:42
  • Added as an example to the post. Remember to do all the code changes from the linked post (view model should provide observable events, fragment should subscribe to those and handle once observed) – Demigod Mar 30 '20 at 15:54
  • but still the event is responsible for navigation here, not the fragment – Morozov Mar 31 '20 at 13:19
-1

Thanks for advice, but i modified this solution with next code:

Fragment:

override fun onItemClick(titleName: Int) {
    viewModel.onTitleClick(titleName)
}

ViewModel:

fun onTitleClick(titleName: Int) {
        when (titleName) {
            R.string.about_terms_service -> {
                termsOfServiceItemClickEvent.postValue(
                    ViewModelEvent(
                        WebViewFragment.newBundle(
                            url = TERMS_LINK,
                            title = appContext.getString(R.string.about_terms_service)
                        )
                    )
                )
            }
            R.string.about_open_source_licenses -> licenseItemClickEvent.postValue(ViewModelEvent(R.string.about_open_source_licenses))
        }
    }

ViewModelEvent:

open class ViewModelEvent<out T>(private val content: T? = null) {

    var hasBeenHandled = false
        private set

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }
}

And again fragment:

viewModel = ViewModelProviders
    .of(this, viewModelFactory)
    .get(AboutViewModel::class.java)

viewModel.licenseItemClickEvent.observe(this, Observer<ViewModelEvent<Int>> {
    it?.getContentIfNotHandled()?.let { activity?.addFragment(LicensesFragment()) }
})
viewModel.termsOfServiceItemClickEvent.observe(this, Observer<ViewModelEvent<Bundle>> {
    it?.getContentIfNotHandled()?.let { args ->
        activity?.addFragment(WebViewFragment.newInstance(args = args))
    }
})
Morozov
  • 4,968
  • 6
  • 39
  • 70