Combined with Jetpack, build a rapidly developed MVVM framework.
The project uses Jetpack: LiveData, ViewModel, Lifecycle, Navigation components.
Support dynamic loading of multi-state layouts: loading, success, failure, and title;
Supports rapid generation of ListActivity and ListFragment;
Supports the use of plug-ins to quickly generate activities, Fragment, ListActivity, and ListFragments suitable for this framework.
Go to the full articleGithubBrowse
Preface
along with
Jetpack
For developers,MVVM
It seems more efficient and convenient.For use
MVVM
All companies have their own set ofMVVM
Framework, but I found that some of them are just very simple encapsulation of the framework, resulting in a lot of unnecessary redundant code during the development process.This article mainly shares how to build an efficient
MVVM
frame.
Rapid development based on MVVM, ready to use. (Refactoring has been completed, SampleApp is being written)
The basic framework is separated into modules
MVVM Library
--MVVM Navigation Library
--MVVM Network Library
Can be used based on business requirementsMVVM Library
、MVVM Navigation Library
、MVVM Network Library
A one-click code template has been developed to create activities and Fragments suitable for this framework. For details, please view them.AlvinMVVMPlugin_4_3
How to integrate
To get a Git project into your build:
Step 1. Add the JitPack repository to your build file
Add it in your root at the end of repositories:
allprojects { repositories { ... maven { url '' } } }
Step 2. Add the dependency
dependencies { // MVVM base class implementation ':mvvm_framework:Tag' // MVVM Network is only responsible for network processing implementation ':mvvm_network:Tag' // MVVM Navigation component extraction implementation ':mvvm_navigation:Tag' }
illustrate | Depend on address | Version number |
---|---|---|
MVVM base class | implementation ':mvvm_framework:Tag' | |
MVVM Network | implementation ':mvvm_network:Tag' | |
MVVM Navigation | implementation ':mvvm_navigation:Tag' |
After the dependency is introduced, the dependency needs to be initialized. The following is the modular initialization process.
1. Inherit BaseApplication
Create yourApplicationkind,inheritBaseApplication
, and need toonCreate
The relevant parameters of the function are configured and initialized. Here you can configure the parameters of the network request framework and the UI global parameters. for exampleInterceptorandMulti-domain name, the overall situationActivityandFragmentproperty.
// Global Activity settings(BaseActivitySetting(), BaseFragmentSetting()) private fun initHttpManager() { // Parameter interceptor { // Set network properties setTimeUnit() // Time type seconds, frame default value milliseconds setReadTimeout(30) // Read timeout 30s, frame default value 10000L setWriteTimeout(30) // Write timeout 30s, frame default value 10000L setConnectTimeout(30) // Link timeout 30s, frame default value 10000L setRetryOnConnectionFailure(true) // Timeout automatically reconnects, the default value of the framework is true setBaseUrl("") // Default domain name // Multi-domain configuration setDomain { { map -> { if (() && ()) { put(, ) } } } } setLoggingInterceptor( isDebug = , hideVerticalLine = true, requestTag = "HTTP Request Request Parameters", responseTag = "HTTP Response Return Parameters" ) // Add an interceptor setInterceptorList(hashSetOf(ResponseInterceptor(), ParameterInterceptor())) } } // It needs to be rewrite, and whether to pass in the current initial Debug mode.override fun isLogDebug(): Boolean { // Whether to display logs return
2. Create ViewModel extension function
All modules need to rely on base modules to create ViewModel-related extension functionsVMKxtand Json entity shellBaseEntity。
/** * Filter server results, fail to throw exception * @param block request body method, must be modified with the suspend keyword * @param success callback * @param error Failed callback, no transmission * @param isLoading Whether to display the Loading layout * @param loadingMessage loading box prompt content */ fun <T> ( block: suspend () -> BaseResponse<T>, success: (T?) -> Unit, error: (ResponseThrowable) -> Unit = {}, isLoading: Boolean = false, loadingMessage: String? = null ): Job { // Start executing the request ( // Execute Loading logic LoadingEntity( isLoading, loadingMessage?.isNotEmpty() == true, loadingMessage ?: "" ) ) return { { //Request body block() }.onSuccess { // The network request is successful, the request ends (false) //Check whether the request result code is correct. If it is not correct, an exception will be thrown and the following onFailure will be taken. { executeResponse(it) { coroutine -> success(coroutine) } }.onFailure { error -> // An exception occurred during request, and the callback failed to execute val responseThrowable = (error) = ?: "" ?.let { errorLog -> (errorLog) } // Callback method for failed execution error(responseThrowable) } }.onFailure { error -> // An exception occurred during request, and the callback failed to execute val responseThrowable = (error) = ?: "" ?.let { errorLog -> (errorLog) } // Callback method for failed execution error(responseThrowable) } } } /** * But filter the server results * @param block request body method, must be modified with the suspend keyword * @param success callback * @param error Failed callback, no transmission * @param isLoading Whether to display the Loading layout * @param loadingMessage loading box prompt content */ fun <T> ( block: suspend () -> T, success: (T) -> Unit, error: (ResponseThrowable) -> Unit = {}, isLoading: Boolean = false, loadingMessage: String? = null ): Job { // Start executing the request ( // Execute Loading logic LoadingEntity( isLoading, loadingMessage?.isNotEmpty() == true, loadingMessage ?: "" ) ) return { runCatching { //Request body block() }.onSuccess { // The network request is successful, the request ends (false) //Successful callback success(it) }.onFailure { error -> // An exception occurred during request, and the callback failed to execute val responseThrowable = (error) = ?: "" ?.let { errorLog -> (errorLog) } // Callback method for failed execution error(responseThrowable) } } } /** * Filter the request result, determine whether the request server request result is successful, and if it fails, an exception will be thrown. */ suspend fun <T> executeResponse( response: BaseResponse<T>, success: suspend CoroutineScope.(T?) -> Unit ) { coroutineScope { when { () -> { success(()) } else -> { throw ResponseThrowable( (), (), () ) } } } }
The above code encapsulates a fast network request extension function, and can choose to unshell or not unshelled callback processing according to your own situation. Call example:
/** * Load list data */ fun getArticleListData(page: Int, pageSize: Int) { request( { filterArticleList(page, pageSize) }, { // Successful operation it?.let { _articleListData.postValue() } } ) }
Complete the above operations and you can enter a pleasant development work.
3. Introduce one-click code generation plug-in (optional)
Every time you create Activity, Fragment, ListActivity, and ListFragment, it is a repetitive work. In order to develop more efficiently and reduce these boring operations, a plug-in specially written to quickly generate MVVM code. This plug-in is only suitable for the current MVVM framework. For details, please go toAlvinMVVMPlugin. After integration, you can start creatingEmptyActivity
Create thisMVVMActivity
。
Framework structure
mvvm
This component encapsulates common attributes for Activity and Fragment
-
base
Packed packagedMVVM
basic components.-
activity
accomplishDataBinding + ViewModel
package, as well as some other features. -
adapter
accomplishDataBinding + Adapter
package. -
fragment
accomplishDataBinding + ViewModel
package, as well as some other features. -
livedata
accomplishLiveData
Basic functional encapsulation, such as non-null return value of the basic data type. -
view_model
accomplishBaseViewModel
handling.
-
-
help
The auxiliary class that encapsulates the component is assigned to the global Activty and Fragment attributes in BaseApplication. -
manager
The package encapsulates the management of Activity. -
utils
The LogUtil tool class is encapsulated under the package and initialized through BaseApplication.
Activity encapsulation
-
AbstractActivity
yesActivity
The abstract base class, the methods in this class are applicable to allActivity
demand. This class encapsulates the abstract methods that all activities must implement. -
BaseActivity
Encapsulated basicActivity
Function, mainly used for initializationActivity
Public functions:DataBinding
initialization, immersive status bar,AbstractActivity
Calling abstract methods, screen adaptation, and hidden soft keyboards in blank areas. Specific functions can be added by yourself. -
BaseDialogActivity
Only responsible for displayDialog Loading
Pop-up windows are generally used when submitting requests or local stream processing. Others can be expandedDialog
, such as time selectors. -
BaseContentViewActivity
It is to initialize the layoutActivity
, He is our core. Each is processed hereActivity
The layout of each state generally includes:-
TitleLayout
Public title -
ContentLayout
The main content layout makes us need the main container of program content. -
ErrorLayout
When an error occurs in a network request, a user-friendly prompt is required. -
LoadingLayout
The layout of data being loaded gives users a good experience and avoids the layout displayed on the page for the first time without data.
-
-
BaseVMActivity
accomplishViewMode
ofActivity
Base class, through generic pairsViewModel
Instantiate. And throughBaseViewModel
Perform public operations. -
BaseMVVMActivity
allActivity
In the end, it needs inheritanceMVVM
Class, passed inDataBinding
andViewModel
The generics are initialized and need to be obtained in the constructor parameters.Layout
layout -
BaseListActivity
Applicable to listActivity
, paging operation, pull-up loading, pull-down refresh, empty layout, head layout, and bottom layout encapsulation.
Fragment Package
Different packaging is required according to your needs, I prefer toActivity
Package with the same function, that isActivity
Package function IFragment
There must be, too. This is usedNavigation
Can be reduced whenActivity
andFragment
The difference. Here we directly refer to the Activity package
Adapter encapsulation
There will definitely be a list page in each project, so you still need toAdapter
conductDataBinding
Adapter used here isBRVAH。
abstract class BaseBindingListAdapter<T, DB : ViewDataBinding>( @LayoutRes private val layoutResId: Int ) : BaseQuickAdapter<T, BaseViewHolder>(layoutResId) { abstract fun convert(holder: BaseViewHolder, item: T, dataBinding: DB?) override fun convert(holder: BaseViewHolder, item: T) { convert(holder, item, ()) } }
LiveData Packaging
LiveData
Data backflow will occur when using it. In simple terms, we can describe data backflow: A subscribes to the news information on January 1, B subscribes to the news information on January 15, but B received the information on January 15 at the same time on January 15, which obviously does not conform to the logic in our lives, so we need to do it.LiveData
Package, please view it in detailKunMinX
of**UnPeek-LiveData**。
Navigation Packaging
By rewriteFragmentNavigator
Put the original one()
Replace the method withhide()/Show()
ViewModel Package
existBaseViewModel
encapsulate a network requestLiveData
, Here is a simple example
open class BaseViewModel : ViewModel() { // Default network request LiveData val httpCallback: HttpCallback by lazy { HttpCallback() } inner class HttpCallback { /** * An error occurred in the request * * String = Network request exception */ val onFailed by lazy { StringLiveData() } /** * Request starts * * LoadingEntity Displays the loading entity class */ val beforeNetwork by lazy { EventLiveData<LoadingEntity>() } /** * After the request is completed, the framework will automatically process loading. * * false Close loading or Dialog * true not to close loading or Dialog */ val afterNetwork by lazy { BooleanLiveData() } } }
Auxiliary encapsulation
Most of theActivity
andFragment
The style is basically the same, such as the layoutTitleLayout
、LoadingLayout
These are all the same styles. Therefore, global auxiliary classes can be encapsulated to extract properties in the Activity.
- Define interface
ISettingBaseActivity
Add the method of extraction and assign the default value. - Define interface
ISettingBaseFragment
Add the method of extraction and assign the default value. - create
ISettingBaseActivity
andISettingBaseFragment
The implementation class performs default custom operations. - create
GlobalMVVMBuilder
Perform assignment
Management encapsulation
passLifecycle
CombinedAppManager
Management of activities in and out.
mvvm_navigation
Separate Navigation and replace the original () method with hide()/Show() by rewriting the FragmentNavigator.
mvvm_network
useRetrofit
+ OkHttp
+ Moshi
Encapsulate network requests and customize exception handling using sealing classes.
This is the end of this article about building a practical MVVM framework from 0. For more related MVVM framework construction content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!