Skip to content

Cook book

codeerow edited this page Jul 17, 2019 · 23 revisions

MVVM relationship

Okay, this is what we need for this essential recipe:

  • Model
  • ViewModel
  • View
  • Dagger/Koin
  1. Model is our business logic. We should make an use case for interacting with it:
/* Use case for fetching string from network/database/... */
class FetchStringUseCase(private val api: StringAPI //imagine such weird API) : UseCase() {

    fun execute(request: FetchStringRequest) : Single<String> {
        return api.fetch(request)
}
  1. Create ViewModel and provide your just created use case:
/* ViewModel for interacting with business logic */
class FetchViewModel @Inject(private val fetchStringUseCase: FetchStringUseCase): MvvmViewModel<Unit> {

    val string = MutableLiveData<String>().apply { value = "" }
    
    fun fetchString() {      
        fetchStringUseCase.execute(FetchRequest())          .doOnSuccess(string::postValue)
            .subscribeByViewModel()
    }
}
  1. And the last, add a view:
class FetchFragment : MvvmFragment() {
    
    override val viewModel by viewModel<AViewModel>()

    
    /* Lifecycle */
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_a, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.string.observe(this, Observer { tvString.text = it })

        RxView.clicks(btnFetchData)
                .subscribeByView { viewModel.fetchData() }
    }
}
  1. After MVVM created you can add as much salt as you want.

Handling ViewState

First of all setup MVVM from the first recipe. Next we need:

  • Use case state
  • ViewModel state
  • One overriding on view
  1. Smoke a joint or drink some Vodka.
  2. UseCase has its own state out of box. Add some state changing for your use case:
class FetchStringUseCase(private val api: StringAPI): UseCase() {
    fun fetchString(request: FetchStringRequest) : Single<String> {
        return api.fetchString(request)
        .doOnSubscribe {state.accept(State.Processing()) }}
        .doOnError { state.accept(State.Failure(listOf(it))) }
        .doOnSuccess { state.accept(State.Success()) }
}
  1. Bind use case state with your ViewModel state. Yeah, each ViewModel has state that represents a view state. By the way, states for use case and view model are the same sealed classes with New, Processing, Failure and Success states.
class FetchViewModel ...

    init {
        // When there is at least one use case with 
        // failure state -> ViewModel state is Failure,
        // if no failures but there is at least one processing use case -> ViewModel state is Processing,
        // else -> ViewModel state is Success
        state.bindsWith(fetchStringUseCase.state)
    }
...
  1. Handle view state on the view. You can observe the whole view state by overriding render or grannualy observe state for use cases:
class FetchFragment ...

override fun onViewCreated(...) {
// Observe use case states for animate something. (e.g. button for fetching string)
        viewModel.fetchStringUseCase.state
            doOnNext(::renderFetchingString)
            subscribeByView()
    }

    override fun render(viewState: State) {
        when(viewState) {
            is New -> renderInitialView()
            is Success -> renderSuccessView()
            is Processing -> renderProcessingView()
            is Failure -> renderFailureView(viewState.failures)
        }
    }
    
    // fun renderInitialView()
    // fun renderFetchingString() 
    // ...
}

Navigation

Navigation with AacFragment/Activity

Pandora have out of box navigation based on Android Architecture Component. But it makes navigation more customizable. We need:

  • Navigation graph
  • AacFragment or AacActivity (navigation holder)
  • Some fragments (screens we will navigate through)
  • Some view models for fragments And it's all!
  1. Create MainActivity and extend it from AacActivity. It means that your activity turns into navigation holder and can handle common navigation command like StartActivity and FinishActivity and special Aac Command:
  • AacGoForward
  • AacGoBackward Navigation command might come from ViewModel or be applying from fragment/activity. I use BaseAacActivity for injecting viewmodels factory.
class MainActivity : BaseAacActivity() {
    override val settings = AacSettings(start)
}

Introduction

  • Core concepts
    • ViewModel
    • View
    • UseCase
    • State

Advanced Concepts

  • Navigation
    • Using out of box navigation
    • Custom navigation
  • UI Configuration
    • Using out of box UI configurations (Toolbar, BottomNavigation)
    • Custom UI Provider/ UI Configuration
  • MVVM relationship, Interact with Business logic, ViewState handling

Clone this wiki locally