SoFunction
Updated on 2025-04-10

Quickly build a practical MVVM framework from 0 (super detailed)

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 withGooglerightJetpackFor developers,MVVMIt seems more efficient and convenient.

For useMVVMAll companies have their own set ofMVVMFramework, 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 efficientMVVMframe.

Rapid development based on MVVM, ready to use. (Refactoring has been completed, SampleApp is being written)

The basic framework is separated into modulesMVVM Library--MVVM Navigation Library--MVVM Network LibraryCan be used based on business requirementsMVVM LibraryMVVM Navigation LibraryMVVM 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 toonCreateThe 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 creatingEmptyActivityCreate thisMVVMActivity

Framework structure

mvvm

This component encapsulates common attributes for Activity and Fragment

  • basePacked packagedMVVMbasic components.
    • activityaccomplishDataBinding + ViewModelpackage, as well as some other features.
    • adapteraccomplishDataBinding + Adapterpackage.
    • fragmentaccomplishDataBinding + ViewModelpackage, as well as some other features.
    • livedataaccomplishLiveDataBasic functional encapsulation, such as non-null return value of the basic data type.
    • view_modelaccomplishBaseViewModelhandling.
  • helpThe auxiliary class that encapsulates the component is assigned to the global Activty and Fragment attributes in BaseApplication.
  • managerThe package encapsulates the management of Activity.
  • utilsThe LogUtil tool class is encapsulated under the package and initialized through BaseApplication.

Activity encapsulation

  • AbstractActivityyesActivityThe abstract base class, the methods in this class are applicable to allActivitydemand. This class encapsulates the abstract methods that all activities must implement.
  • BaseActivityEncapsulated basicActivityFunction, mainly used for initializationActivityPublic functions:DataBindinginitialization, immersive status bar,AbstractActivityCalling abstract methods, screen adaptation, and hidden soft keyboards in blank areas. Specific functions can be added by yourself.
  • BaseDialogActivityOnly responsible for displayDialog LoadingPop-up windows are generally used when submitting requests or local stream processing. Others can be expandedDialog, such as time selectors.
  • BaseContentViewActivityIt is to initialize the layoutActivity, He is our core. Each is processed hereActivityThe layout of each state generally includes:
    • TitleLayoutPublic title
    • ContentLayoutThe main content layout makes us need the main container of program content.
    • ErrorLayoutWhen an error occurs in a network request, a user-friendly prompt is required.
    • LoadingLayoutThe layout of data being loaded gives users a good experience and avoids the layout displayed on the page for the first time without data.
  • BaseVMActivityaccomplishViewModeofActivityBase class, through generic pairsViewModelInstantiate. And throughBaseViewModelPerform public operations.
  • BaseMVVMActivityallActivityIn the end, it needs inheritanceMVVMClass, passed inDataBindingandViewModelThe generics are initialized and need to be obtained in the constructor parameters.Layoutlayout
  • BaseListActivityApplicable 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 toActivityPackage with the same function, that isActivityPackage function IFragmentThere must be, too. This is usedNavigationCan be reduced whenActivityandFragmentThe 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 toAdapterconductDataBindingAdapter 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

LiveDataData 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.LiveDataPackage, please view it in detailKunMinXof**UnPeek-LiveData**。

Navigation Packaging

By rewriteFragmentNavigatorPut the original one()Replace the method withhide()/Show()

ViewModel Package

existBaseViewModelencapsulate 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&lt;LoadingEntity&gt;() }

        /**
          * 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 theActivityandFragmentThe style is basically the same, such as the layoutTitleLayoutLoadingLayoutThese are all the same styles. Therefore, global auxiliary classes can be encapsulated to extract properties in the Activity.

  • Define interfaceISettingBaseActivityAdd the method of extraction and assign the default value.
  • Define interfaceISettingBaseFragmentAdd the method of extraction and assign the default value.
  • createISettingBaseActivityandISettingBaseFragmentThe implementation class performs default custom operations.
  • createGlobalMVVMBuilderPerform assignment

Management encapsulation

passLifecycleCombinedAppManagerManagement 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 + MoshiEncapsulate 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!