MVVMLin

A rapid development framework based on MVVM encapsulated with Kotlin+Retrofit+ coroutine +Databinding+LiveData: project address: MVVMLin

Github also has a lot of frameworks for MVVM. We’ve been working on MVP projects with RxJava +Retrofit, but now AndroidX is the trend, Kotlin has been an official language for two years, and there are new things coming out at GoogleIO this year, ~~~~. The recent project is not too busy, put these new things together, encapsulate a FRAMEWORK of MVVM, share it to everyone. After ditching the powerful RxJava, the heart is still a little empty.

Introduction of framework

  • The use of the technical

Kotlin + coroutine + Retrofit + LiveData +DataBinding is used based on the MVVM pattern

  • The basic package

It is more convenient to encapsulate BaseActivity, BaseFragment and BaseViewModel based on concordant network. Considering that some partners do not like to use DataBinding to bind data in XML, it also provides corresponding adaptation. The two ways to choose by themselves. Retrofit2.6 provides support for coroutines, making it easier to use, regardless of type conversions.

  • The characteristics of

We will use AutoDispose, RxLifecycle, etc., but to use coroutines to request data, do not worry about this problem. All requests are started in viewModelScope. When the page is destroyed, It will all be cancelled. Don’t worry about it. Kotlin package, a lot of grammar sugar is easy to use.

  • Introducing third-party libraries

AndroidUtilCode: Contains a large number of common utility classes, it is a must.

Material – dialogs: popup window

glide: Image loading (deprecated 1.0.5)

Coil: image loading (more suitable for KT image loading)

Retrofit: Network requests

1. How to use it

1.1 enable databinding

Build. Gradle android {} in the main project app:

dataBinding {
    enabled true
}
Copy the code

1.2 depend on

Build. Gradle in the main project app

dependencies { ... Implementation 'me. Aleyn: MVVMLin: 1.0.5'}Copy the code

Or download to the local import Module

1.3 Configuring dependency on the version file config.gradle

Copy Demo config,gradle to root directory, add to build. Gradle :(remote dependencies can be ignored)

apply from: "config.gradle"
Copy the code

2. Start fast

2.1 the Activity

Inheritance BaseActivity

class DetailActivity : BaseActivity<NoViewModel, ViewDataBinding>() { override fun layoutId() = R.layout.activity_detail override fun initView(savedInstanceState: Bundle?) {... } override fun initData() { .... }}Copy the code

The first generic is VIewModel, so if the page is simple and you don’t need a VIewModel, you just pass NoViewModel. The second generics is Databinding. If the page uses Databinding, the generated Binding class is passed. If the page does not use Databinding, the ViewDataBinding base class is passed in the normal way without initializing an mBinding. The layoutId() method returns the corresponding layout initView() and initData() as the default implementation, and initializes the UI

2.2 fragments

Inheritance BaseFragment

class HomeFragment : BaseFragment<HomeViewModel, ViewDataBinding>() { override fun layoutId() = R.layout.home_fragment override fun initView(savedInstanceState: Bundle?) { } override fun lazyLoadData() { .... }}Copy the code

The Fragment method lazyLoadData() can be overridden as an Activity. The setUserVisibleHint() method has been deprecated and lazy loading is implemented in a new way.

If you don’t want to use Databinding in your Fragment, generics ViewDataBinding:

2, Using DataBinding, layout file:

<layout>
    <data>
    .....
    </data>
    .....
</layout>
Copy the code

Generics correspond to generating the Binding class:

class ProjectFragment : BaseFragment<ProjectViewModel, ProjectFragmentBinding>() {
		.........
}
Copy the code
2.3 the ViewModel

Inheritance BaseViewModel

class HomeViewModel : BaseViewModel() {
		.........
}
Copy the code

If a page has very little content and doesn’t need a ViewModel, we might not want to create another ViewModel class, just generically pass NoViewModel. BaseVIewModel has a simple encapsulation of the coroutine, BaseViewMode has done the unified processing of network request exceptions. For example, our network request could be written like this:

class HomeViewModel : BaseViewModel() { private val homeRepository by lazy { InjectorUtil.getHomeRepository() } val mBanners = MutableLiveData<List<BannerBean>>() fun getBanner() { All other throw the custom exception launchOnlyresult ({homeRepository. GetBannerData ()}, {mBanners. Value = it})}}Copy the code

So what if we want to handle the error ourselves?

class HomeViewModel : BaseViewModel() { private val homeRepository by lazy { InjectorUtil.getHomeRepository() } val mBanners = MutableLiveData<List<BannerBean>>() fun getBanner() { launchOnlyresult({ homeRepository.getBannerData() }, {mBanners value = it}, {/ / here is the Error returned ()})}}Copy the code

All you need to do is add a method parameter.

Another way to return results without filtering:

class MeViewModel : BaseViewModel() {
    private val homeRepository by lazy { InjectorUtil.getHomeRepository() }
    var popularWeb = MutableLiveData<List<UsedWeb>>()
    fun getPopularWeb() {
        launch({
            val result = homeRepository.getPopularWeb()  //
            if (result.isSuccess()) {
                popularWeb.value = result.data
            }
        })
    }
}
Copy the code

To handle the Error yourself, just add a method argument as above.

Each network request has a wait box, if we don’t want a wait box:

 fun getProjectType() {
        launchOnlyresult({
            homeRepository.getNaviJson()
        }, {
            navData.addAll(it)
            it.forEach { item ->
                navTitle.add(item.name)
            }
        }, isShowDialog = false)
 }
Copy the code

IsShowDialog passes false, default is true

In the form of a Flow:

fun getFirstData() { launchUI { launchFlow { homeRepository.getNaviJson() } .flatMapConcat { return@flatMapConcat if (it.isSuccess()) { navData.addAll(it.data) it.data.forEach { item -> navTitle.add(item.name) } launchFlow { homeRepository.getProjectList(page, it.data[0].id) } } else throw ResponseThrowable(it.errorCode, it.errorMsg) } .onStart { defUI.showDialog.postValue(null) } .flowOn(Dispatchers.IO) .onCompletion { DefUI. DismissDialog. Call ()}. Catch {/ / error handling val. Err = ExceptionHandle handleException LogUtils (it), d (" ${err. Code} : ${err.errMsg}") } .collect { if (it.isSuccess()) items.addAll(it.data.datas) } } }Copy the code
2.4 IBaseResponse

Since the request relies on the base class, the base class fields of each project are different, so after we define the base class according to the background fields, we need to implement the IBaseResponde interface, as follows:

data class BaseResult<T>( val errorMsg: String, val errorCode: Int, val data: T ) : IBaseResponse<T> { override fun code() = errorCode override fun msg() = errorMsg override fun data() = data override fun  isSuccess() = errorCode == 0 }Copy the code

To ensure the normal use of filtering request results

Example 3,

The Demo shows only three ways to use lists

3.1 Do not use Databinging, combineBRVAH

See HomeFragment in Demo

3.2 Using Databinging, combinebindingcollectionadapter

The combination of BindingCollectionAdapter does not need to write Adapter Adapter, as shown in the ProjectFragment section of Demo

3.2 Using Databinging, combineBRVAH

BRVAH also has support for DataBinding, as shown in the MeFragment section of Demo

4. Framework

The follow-up will be further improved, we give more comments

QQ group (791382057)