preface

Actually write MVVM, right? Messing around? And MVVM? I didn’t feel water when I continued to write these two articles, but after I read them, I felt that there was nothing about technology except some pictures, which is very bullshit, the technical article didn’t talk about technology…

What follows is likely to offend many authors, myself included…

In fact, a lot of people write android for granted, yes, just for granted, or maybe just me.

The online talk about MVC, MVP, MVVM and so on, all hype, including the two articles I posted before, said good but there is no example, there is no thought, no, sometimes there is thought, but the example is too simple to be useless, for example, I have seen many authors write MVP articles, What is said about the MVP means make a lot of space, besides, compared with the MVC what are the advantages and write a lot, a lot of theory, practice to write when I was a login page, is that the ideological sense, how to write ah, company’s android project is light to write a login page, and a login page with the MVP, To write more code? Increase the amount of code? It’s ok to write a login page, but how to build the whole project? How to deal with special cases? It’s all about feeling for stones!

So PREPARE to write a series of articles, from the beginning of the project construction, step by step to write a small project process and ideas to explain clearly, this is the reason for the birth of the article.

The project is the MVVM version of android written before, the interface is ready-made by Hong Yang God, we can download APK first experience: www.pgyer.com/llj2

Then put down the Github address of the project: github.com/zhujiang521…

The body of the

Maybe a lot of people don’t want to see what I wrote above… That also write what body ah. But,, there should be some people did not scold me, then can continue to write.

If you had to write a project from scratch, what would be your first step?

Did I get you? How many of you have been maintaining projects in your company for a long time? Come on, do it again today!

MVC, MVP although can not say outdated, but after all, it is a new project, must use the latest MVVM, again to the cliche question, what is MVVM? In fact, the first two articles have explained enough, here is not repeated, and the focus of this article is how to use!

Step 1 — Write the base class

I don’t know what your code habits are, but my personal habit is to build a base class for a new project and then do it, because if you don’t write a base class and then extract it, it’s a hassle, but what’s the base class for Android? It must be BaseActivity and BaseFragment.

When it comes to BaseActivity and BaseFragment, it’s important to think about what to write here, because it has to be methods that most classes can use, and something to leave to subclasses to implement. At this point, it’s important to think about the methods that most classes can use. Those of you who have seen the project description should know that the project implements five different states: Normal display of content, loading, no network, no content, loading errors, obviously, all of these should be written in BaseActivity and BaseFragment, so here comes the exciting code!

1 and 1 BaseActivity
abstract class BaseActivity : AppCompatActivity() {/** * The Activity displays the control that is waiting to load. * /
    private var loading: ProgressBar? = null

    /** * The layout displayed in the Activity that failed to load due to a server exception. * /
    private var loadErrorView: View? = null

    /** * The layout displayed in the Activity that failed to load due to a network exception. * /
    private var badNetworkView: View? = null

    /** * Displays the layout of the Activity when there is nothing on the screen. * /
    private var noContentView: View? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setupViews()
    }
  
    protected open fun setupViews(a) {
        loading = findViewById(R.id.loading)
        noContentView = findViewById(R.id.noContentView)
        badNetworkView = findViewById(R.id.badNetworkView)
        loadErrorView = findViewById(R.id.loadErrorView)
        if (loading == null) {
            Log.e(TAG, "loading is null")}if (badNetworkView == null) {
            Log.e(TAG, "badNetworkView is null")}if (loadErrorView == null) {
            Log.e(TAG, "loadErrorView is null")}}companion object {

        private const val TAG = "BaseActivity"}}Copy the code

All right, this much, too much will get you crazy… Take a look at the code: If you set it as an abstract class, you need to learn the Java foundation. Then you need to find all the views you need. Then you need an interface, you need to extract the life cycle functions that need to go through in the Activity or Fragment. So BaseActivity and BaseFragment can be reused as soon as possible:

interface RequestLifecycle {

    fun startLoading(a)

    fun loadFinished(a)

    fun loadFailed(msg: String?).

}
Copy the code

So next, the BaseActivity should be modified:

abstract class BaseActivity : AppCompatActivity(), RequestLifecycle {

    /** * This method displays a prompt to the user when the Activity fails to load the content server. * *@paramTip * Prompt information on the interface */
    protected fun showLoadErrorView(tip: String = "Failed to load data") {
        loadFinished()
        if(loadErrorView ! =null) {
            valloadErrorText = loadErrorView? .findViewById<TextView>(R.id.loadErrorText) loadErrorText? .text = tip loadErrorView? .visibility = View.VISIBLEreturn}}/** * This method displays a prompt to the user when the content in the Activity cannot be displayed due to network reasons. * *@paramListener * Reloads click event callback */
    protected fun showBadNetworkView(listener: View.OnClickListener) {
        loadFinished()
        if(badNetworkView ! =null) { badNetworkView? .visibility = View.VISIBLE badNetworkView? .setOnClickListener(listener)return}}/** * This method displays a prompt to the user when there is no content in the Activity. *@paramTip * Prompt information on the interface */
    protected fun showNoContentView(tip: String) {
        loadFinished()
        valnoContentText = noContentView? .findViewById<TextView>(R.id.noContentText) noContentText? .text = tip noContentView? .visibility = View.VISIBLE }/** * Hide the Load Error View. * /
    private fun hideLoadErrorView(a){ loadErrorView? .visibility = View.GONE }/** * Hide the no content view. * /
    private fun hideNoContentView(a){ noContentView? .visibility = View.GONE }/** * Hide the bad Network View. * /
    private fun hideBadNetworkView(a){ badNetworkView? .visibility = View.GONE }@CallSuper
    override fun startLoading(a){ hideBadNetworkView() hideNoContentView() hideLoadErrorView() loading? .visibility = View.VISIBLE }@CallSuper
    override fun loadFinished(a){ loading? .visibility = View.GONE hideBadNetworkView() hideNoContentView() hideLoadErrorView() }@CallSuper
    override fun loadFailed(msg: String?).{ loading? .visibility = View.GONE hideBadNetworkView() hideNoContentView() hideLoadErrorView() } }Copy the code

Now that I’ve got the general idea, here’s the @callsuper annotation: it means that any overriding method should call this method. What’s next? As mentioned earlier, some classes can be used, and can be implemented by the parent class we have implemented, there is also a need to subclass to implement, such as: Loading a layout, page, data, etc., both activities and fragments must be subclasses, so you can write an interface to extract them:

interface BaseInit {

    fun initData(a)

    fun initView(a)

    fun getLayoutId(a): Int

}
Copy the code

Load the data, load the View, get the layout, and then you can continue to improve the BaseActivity:

abstract class BaseActivity : AppCompatActivity(), RequestLifecycle, BaseInit {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        transparentStatusBar()
        setContentView(getLayoutId())
        initView()
        initData()
    }

    override fun setContentView(layoutResID: Int) {
        super.setContentView(layoutResID)
        setupViews()
    }

    /** * Make the status bar transparent. It only works with Android 5.0 or higher. * /
    private fun transparentStatusBar(a) {
        if (AndroidVersion.hasLollipop()) {
            val decorView = window.decorView
            decorView.systemUiVisibility =
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            window.statusBarColor = Color.TRANSPARENT
        }
    }

}
Copy the code

This does two things: 1. Implements the initialized interface and calls it; 2. Make the status bar transparent, because all apps are currently immersive.

Don’t know you notice no, directly in the parent class can implement the interface implementation, but need to implemented in subclasses interface all have no, this is equivalent to write directly in the parent class abstraction method, in order to and BaseFragment reuse, so extraction interface, but not implemented in the parent class to a subclass to implement.

Finally, add one more function to the BaseActivity: An Activity controller, there are a lot of cases where we want to shut down an Activity that we started earlier, but that’s a hassle, so we want to implement an Activity controller that adds it to the controller every time the Activity executes onCreate, Remove the onDestroy method from the controller, go ahead, write one:

object ActivityCollector {

    private const val TAG = "ActivityCollector"

    private valactivityList = ArrayList<WeakReference<Activity>? > ()fun size(a): Int {
        return activityList.size
    }

    fun add(weakRefActivity: WeakReference<Activity>? {
        activityList.add(weakRefActivity)
    }

    fun remove(weakRefActivity: WeakReference<Activity>? {
        val result = activityList.remove(weakRefActivity)
        Log.d(TAG, "remove activity reference $result")}fun finishAll(a) {
        if (activityList.isNotEmpty()) {
            for (activityWeakReference in activityList) {
                valactivity = activityWeakReference? .get(a)if(activity ! =null && !activity.isFinishing) {
                    activity.finish()
                }
            }
            activityList.clear()
        }
    }

}
Copy the code

The above class is simple, just an ArrayList to add and remove, but it’s important to note that weak references are used to prevent memory leaks.

Now it’s time to add this controller to the BaseActivity:

    private var weakRefActivity: WeakReference<Activity>? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        ActivityCollector.add(WeakReference(this))
        weakRefActivity = WeakReference(this)}override fun onDestroy(a) {
        super.onDestroy()
        ActivityCollector.remove(weakRefActivity)
    }
Copy the code

Ok, that’s it for BaseActivity. Put a full version:

abstract class BaseActivity : AppCompatActivity(), RequestLifecycle, BaseInit {

    /** * The Activity displays the control that is waiting to load. * /
    private var loading: ProgressBar? = null

    /** * The layout displayed in the Activity that failed to load due to a server exception. * /
    private var loadErrorView: View? = null

    /** * The layout displayed in the Activity that failed to load due to a network exception. * /
    private var badNetworkView: View? = null

    /** * Displays the layout of the Activity when there is nothing on the screen. * /
    private var noContentView: View? = null

    private var weakRefActivity: WeakReference<Activity>? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        transparentStatusBar()
        setContentView(getLayoutId())
        ActivityCollector.add(WeakReference(this))
        weakRefActivity = WeakReference(this)
        initView()
        initData()
    }

    override fun onDestroy(a) {
        super.onDestroy()
        ActivityCollector.remove(weakRefActivity)
    }

    override fun setContentView(layoutResID: Int) {
        super.setContentView(layoutResID)
        setupViews()
    }

    protected open fun setupViews(a) {
        loading = findViewById(R.id.loading)
        noContentView = findViewById(R.id.noContentView)
        badNetworkView = findViewById(R.id.badNetworkView)
        loadErrorView = findViewById(R.id.loadErrorView)
        if (loading == null) {
            Log.e(TAG, "loading is null")}if (badNetworkView == null) {
            Log.e(TAG, "badNetworkView is null")}if (loadErrorView == null) {
            Log.e(TAG, "loadErrorView is null")}}/** * Make the status bar transparent. It only works with Android 5.0 or higher. * /
    private fun transparentStatusBar(a) {
        if (AndroidVersion.hasLollipop()) {
            val decorView = window.decorView
            decorView.systemUiVisibility =
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            window.statusBarColor = Color.TRANSPARENT
        }
    }

    /** * This method displays a prompt to the user when the Activity fails to load the content server. * *@paramTip * Prompt information on the interface */
    protected fun showLoadErrorView(tip: String = "Failed to load data") {
        loadFinished()
        if(loadErrorView ! =null) {
            valloadErrorText = loadErrorView? .findViewById<TextView>(R.id.loadErrorText) loadErrorText? .text = tip loadErrorView? .visibility = View.VISIBLEreturn}}/** * This method displays a prompt to the user when the content in the Activity cannot be displayed due to network reasons. * *@paramListener * Reloads click event callback */
    protected fun showBadNetworkView(listener: View.OnClickListener) {
        loadFinished()
        if(badNetworkView ! =null) { badNetworkView? .visibility = View.VISIBLE badNetworkView? .setOnClickListener(listener)return}}/** * This method displays a prompt to the user when there is no content in the Activity. *@paramTip * Prompt information on the interface */
    protected fun showNoContentView(tip: String) {
        loadFinished()
        valnoContentText = noContentView? .findViewById<TextView>(R.id.noContentText) noContentText? .text = tip noContentView? .visibility = View.VISIBLE }/** * Hide the Load Error View. * /
    private fun hideLoadErrorView(a){ loadErrorView? .visibility = View.GONE }/** * Hide the no content view. * /
    private fun hideNoContentView(a){ noContentView? .visibility = View.GONE }/** * Hide the bad Network View. * /
    private fun hideBadNetworkView(a){ badNetworkView? .visibility = View.GONE }@CallSuper
    override fun startLoading(a){ hideBadNetworkView() hideNoContentView() hideLoadErrorView() loading? .visibility = View.VISIBLE }@CallSuper
    override fun loadFinished(a){ loading? .visibility = View.GONE hideBadNetworkView() hideNoContentView() hideLoadErrorView() }@CallSuper
    override fun loadFailed(msg: String?).{ loading? .visibility = View.GONE hideBadNetworkView() hideNoContentView() hideLoadErrorView() }companion object {

        private const val TAG = "BaseActivity"}}Copy the code
BaseFragment

In fact, BaseFragment and BaseActivity are basically the same, but the loading layout is different.

    /** * The layout is inflate out of the Fragment. * /
    private var rootView: View? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        val view = inflater.inflate(getLayoutId(), container, false)
        onCreateView(view)
        return view
    }
    
        /** * Retrieve the generic control in the Fragment base class, and return the View instance as it was passed in. *@paramView instances that are inflate out of view * Fragment. *@returnView instances that are inflate out of the Fragment are returned intact. * /
    private fun onCreateView(view: View): View {
        rootView = view
        loading = view.findViewById(R.id.loading)
        noContentView = view.findViewById(R.id.noContentView)
        badNetworkView = view.findViewById(R.id.badNetworkView)
        loadErrorView = view.findViewById(R.id.loadErrorView)
        if (loading == null) {
            throw NullPointerException("loading is null")}if (badNetworkView == null) {
            throw NullPointerException("badNetworkView is null")}if (loadErrorView == null) {
            throw NullPointerException("loadErrorView is null")}return view
    }
Copy the code

In BaseActivity, if the View is empty, I only print log values, but in BaseFragment, I throw exceptions! If you think your implementation has to implement LCE, just throw it out so you can see the problem when you run it. If you don’t need to print a log value, nothing special.

LCE layout

The layout mentioned in both BaseActivity and BaseFragment has not been written yet. Write down the layout:

One by one, start with the content-free layout!


      
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/wall">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:orientation="vertical">

        <ImageView
            android:layout_width="@dimen/dp_80"
            android:layout_height="@dimen/dp_80"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/no_content_image" />

        <TextView
            android:id="@+id/noContentText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/dp_20"
            android:layout_marginBottom="@dimen/dp_20"
            android:textSize="@dimen/sp_13"
            android:textColor="@color/secondary_text"
            tools:text="There is no more."/>

    </LinearLayout>

</RelativeLayout>

Copy the code

Again, no network layout:


      
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/badNetworkRootView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/wall"
    android:focusable="true"
    android:foreground="? android:selectableItemBackground">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:orientation="vertical">

        <ImageView
            android:layout_width="@dimen/dp_74"
            android:layout_height="@dimen/dp_88"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/bad_network_image" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/dp_20"
            android:layout_marginBottom="@dimen/dp_20"
            android:text="@string/bad_network_view_tip"
            android:textColor="@color/secondary_text"
            android:textSize="@dimen/sp_13" />

    </LinearLayout>

</RelativeLayout>

Copy the code

Next is loading the wrong layout:


      
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/wall">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:orientation="vertical">

        <ImageView
            android:layout_width="@dimen/dp_74"
            android:layout_height="@dimen/dp_88"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/bad_network_image" />

        <TextView
            android:id="@+id/loadErrorText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/dp_20"
            android:layout_marginBottom="@dimen/dp_20"
            android:textColor="@color/secondary_text"
            android:textSize="@dimen/sp_13"
            tools:text="Loading failed" />

    </LinearLayout>

</RelativeLayout>

Copy the code

And the loading layout:


      
<ProgressBar
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/loading"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="@dimen/dp_64"
    android:layout_gravity="center"
    android:indeterminate="true" />
Copy the code

Finally, you need to combine all of these into a single layout:


      
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/loading"
        android:visibility="gone"/>

    <include
        android:id="@+id/noContentView"
        layout="@layout/no_content_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"/>

    <include
        android:id="@+id/badNetworkView"
        layout="@layout/bad_network_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"/>

    <include
        android:id="@+id/loadErrorView"
        layout="@layout/load_error_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"/>

</FrameLayout>
Copy the code

Step 2 — Use BaseActivity

This section was originally intended to write the home page, but after thinking of too many things, I chose a page that does not need to be connected to the Internet — Browsing history. This page not only inherits BaseActivity, but also switches the status of whether there is content, loading, content and so on, so it is suitable.

Let’s take a look at the layout:


      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".view.profile.history.BrowseHistoryActivity">

    <com.zj.core.util.TitleBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:backImageVisiable="true"
        app:titleName="Browsing History" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.scwang.smartrefresh.layout.SmartRefreshLayout
            android:id="@+id/historySmartRefreshLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/historyRecycleView"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

        </com.scwang.smartrefresh.layout.SmartRefreshLayout>

        <include layout="@layout/layout_lce" />

    </FrameLayout>

</LinearLayout>
Copy the code

Layout_lce is the layout of the state we just wrote. TitleBar is a header layout that I customized. You can set the title, left button, right button, button click event, picture or question directly. You can download it from Github to use it.

Since this page does not need to be handled vertically or horizontally, only one page can be written.

Now that the layout is in place, you can start using BaseActivity in earnest:

class BrowseHistoryActivity : ArticleCollectBaseActivity() {

  	private lateinit var articleAdapter: ArticleAdapter
    private var page = 1
  
    override fun getLayoutId(a): Int {
        return R.layout.activity_browse_history
    }

    override fun initView(a) {
        historyRecycleView.layoutManager = LinearLayoutManager(this)
        articleAdapter = ArticleAdapter(
            this,
            R.layout.adapter_article,
            // viewmodel.articlelist, // data source
            false
        )
        articleAdapter.setHasStableIds(true)
        historyRecycleView.adapter = articleAdapter
        historySmartRefreshLayout.apply {
            setOnRefreshListener { reLayout ->
                reLayout.finishRefresh(measureTimeMillis {
                    page = 1
                    GetArticleList () // load data
                }.toInt())
            }
            setOnLoadMoreListener { reLayout ->
                val time = measureTimeMillis {
                    page++
                     GetArticleList () // load data
                }.toInt()
                reLayout.finishLoadMore(if (time > 1000) time else 1000)}}}override fun initData(a) {
        GetArticleList () // load data
    }

    companion object {
        fun actionStart(context: Context) {
            val intent = Intent(context, BrowseHistoryActivity::class.java)
            context.startActivity(intent)
        }
    }

}
Copy the code

The code above uses BaseActivity, as you can see, which is basically the same as normal Activity, but much simpler. The accompanying method at the bottom gives other classes a way to jump to the current class. There’s no advantage here, but it works fine if you need to pass other parameters. Can effectively avoid error parameters.

There is still some content left in the above class, the rest is MVVM content, the next module says.

Step 3 – Use MVVM

I believe that I have read the previous two articles of the old drivers have been able to use, to review it again!

VM also said before, it’s not a ViewModel but it’s also, if you don’t understand it you can look at the previous article. Take a look at the ViewModel:

class BrowseHistoryViewModel(application: Application) : AndroidViewModel(application) {

    private val pageLiveData = MutableLiveData<Int> ()val articleList = ArrayList<Article>()

    val articleLiveData = Transformations.switchMap(pageLiveData) { page ->
        BrowseHistoryRepository(application).getBrowseHistory(page)
    }

    fun getArticleList(page: Int) {
        pageLiveData.value = page
    }

}
Copy the code

Isn’t that easy? ViewModel + LiveData, that’s it, that’s easy, isn’t it?

Notice that you’re using the AndroidViewModel. We usually use the ViewModel, sometimes in order to get the Context we need to pass in a separate parameter, and the ViewModel is a bit of a hassle to pass in parameters, and we need to use Factory to pass in parameters, and in that case we can use AndroidViewModel, Can be directly inherited to use, use the same as before can:

private val viewModel by lazy { ViewModelProvider(this).get(BrowseHistoryViewModel::class.java) }
Copy the code

Is it possible to Get a knowledge point, write it down quickly!

The previous code in the fetch data are commented, now look at it!

    private fun getArticleList(a) {
        if (viewModel.articleList.size <= 0) {
            startLoading()
        }
        viewModel.getArticleList(page)
    }

    override fun initData(a) {
        viewModel.articleLiveData.observe(this, {
            if (it.isSuccess) {
                val articleList = it.getOrNull()
                if(articleList ! =null) {
                    loadFinished()
                    if (page == 1 && viewModel.articleList.size > 0) {
                        viewModel.articleList.clear()
                    }
                    viewModel.articleList.addAll(articleList)
                    articleAdapter.notifyDataSetChanged()
                } else {
                    showLoadErrorView()
                }
            } else {
                if (viewModel.articleList.size <= 0) {
                    showNoContentView("No current browsing history")}else {
                    showToast("No more data.")
                    loadFinished()
                }
            }
        })
        getArticleList()
    }
Copy the code

The BaseActivity method is the same as the BaseActivity method. StartLoading (), loadFinished(), showLoadErrorView(), showNoContentView(“”), etc. In fact, the principle is very simple, according to the state of the data to display different pages.

Take a look at the BrowseHistoryRepository code again:

class BrowseHistoryRepository(context: Context) {

    private val browseHistoryDao = PlayDatabase.getDatabase(context).browseHistoryDao()

    /** * get the history list */
    fun getBrowseHistory(page: Int) = fire {
        val projectClassifyLists = browseHistoryDao.getHistoryArticleList((page - 1) * 20,HISTORY)
        if (projectClassifyLists.isNotEmpty()) {
            Result.success(projectClassifyLists)
        } else {
            Result.failure(RuntimeException("response status is "))}}}Copy the code

The Activity displays the page, the Repository retrieves the data, and the ViewModel processes the data and temporarily stores it for use by the Activity.

The database must use Room, here to mention, if you have not used Room must use it, if you are using Kotlin, especially use it, Room with coroutine is not too sweet! A single line of code like the one above produces the result directly, without the need for thread switching, because that’s what coroutines are good at.

conclusion

I wanted to write a lot of things, but I didn’t know how to describe them when I started writing. I also blame myself. I used to write several articles every month to exercise myself, but now I only write one article a month.

This article is just an overview, the first in this series, to tell you what the mysterious things are and how to use them. The next article will take you to see how the front page of the project is built step by step.

I feel MVVM is not so mysterious, but I don’t have a proper project to practice at work, so I feel strange to understand it. In fact, compared with MVP and MVC, the logic is clearer and more convenient. After all, there is an official JetPack to support it, which must be very delicious! Although the article is not long, it is also not short, and there are many details that have not been written. If you have any questions, you can tell me in the comment section, or you can tell me what you want me to write. I will insist on updating my blog at least two times a month in the future!! If you are helpful, don’t forget the three companies, thank 🙏!