LifeClean is a variant of the MVP framework that applies to common UI businesses and defines some usage specifications for common Android components, with the following features:

  1. specificationMVPwriting
  2. forPresenter/ViewprovideLifecycle
  3. Timely releaseRxJava DisposableTo avoid content leakage
  4. specificationRecyclerView.AdapterThe use of
  5. Regulate the refresh of global UI state

specificationMVPwriting

In my opinion, MVP is mainly used for separation of responsibilities, i.e. Presenter is responsible for data loading logic, and View is responsible for data display logic.

The traditional MVP approach is to extract an interface from both Presenter and View, and then implement isolation between classes using these interfaces.

LifeClean doesn’t extract an interface for every Presenter. LifeClean says:

  1. All of thePresenterAll should follow the same convention (interface)
  2. All of theViewShould be usedPresenterInterface withPresenterinteraction

Abstract Presenter interface:

Interface State interface Presenter {fun dispatch(Action: Action) fun <T : State> getState(): T? {return null
    }

}
Copy the code

It defines the ability of Presenter:

  • Dispatch (Action) : An Action that a Presenter can receive from a View

  • GetState ():T: Presenter returns some State to the View.

These actions/states belong to the View in LifeClean, and the View should define these actions/states in the convention (interface) that it follows, such as:

Based on RecyclerView to achieve the convention of the page:

Interface SimpleRvPageProtocol {// LoadData class LoadData(val searchWord: String, val isLoadMore: Boolean) : Class PageState(val currentPageSize: Int) : State fun refreshDatas(datas: List<Any>, isLoadMore: Boolean =false, extra: Any = Any()) fun refreshPageStatus(status: String, extra: Any = Any())}Copy the code

A concrete use example with Presenter:

//View class GitRepoMvpPage(activity: AppCompatActivity) : SimpleRvPageProtocol, FrameLayout(Activity) {private val Presenter: Presenter = GithubPresenter (this) init {/ / inform loading Presenter. The Presenter do data dispatch (SimpleRvPageProtocol. LoadData ("Android".false)) } override fun refreshDatas(datas: List<Any>, isLoadMore: Boolean, extra: Any) {/ / query data state val currentPageSize = presenter. GetState < SimpleRvPageProtocol. PageState > ()? .currentPageSize ? : 0 Toast.makeText(context,"Current page:$currentPageSize", Toast.LENGTH_SHORT).show()
    }
    ...
}

//Presenter
class GithubPresenter(val view: SimpleRvPageProtocol) : Presenter {

    private var page = 0

    override fun dispatch(action: Action) {
        when (action) {
            is SimpleRvPageProtocol.LoadData -> {
                ...
                view.refreshDatas(list)
            }
        }
    }

    override fun <T : State> getState(): T? {
        return SimpleRvPageProtocol.PageState(page) as? T
    }
}
Copy the code

inLifeCleanLt.ViewDefined as the center of the business, willPresenterThe capabilities (actions) are definedViewIn (agreement),PresenterYou can do this selectivelyAction, i.e.,ViewCompletely decoupled from phiPresenter

forPresenter/ViewprovideLifeCycle

Provide the Activity lifecycle for presenters

Compilers are used to load resources in Presenter, such as RxJava for network requests. How to release compilers in time to avoid memory leakage?

In LifeClean, a Presenter can observe the lifecycle of an Activity by inheriting from LifePresenter:

class GithubPresenter(val view: SimpleRvPageProtocol) : LifePresenter() {

    override fun onActivityCreate() {
        Log.d(TAG, "onActivityCreate")}}Copy the code

LifePresenter inherits LifePresenter and then duplicates the Activity lifecycle methods. Why does LifePresenter have an Activity lifecycle? Internal implementation is as follows:

abstract class LifePresenter : Presenter, LifecycleObserver {

    private var lifeOwnerReference = WeakReference<AppCompatActivity>(null)

    fun injectLifeOwner(lifecycleOwner: AppCompatActivity) {
        lifeOwnerReference = WeakReference(lifecycleOwner)
        lifecycleOwner.lifecycle.addObserver(this)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    open fun onActivityCreate() {}}Copy the code

LifePresenter is a LifecycleObserver and it is an observer for LifeCycle. Where is injectLifeOwner() called?

In fact inLifeCleanIf you want to letPresenterperceptionActivityLife cycle, then must inheritLifePresenterAnd useLifeCleanProvided template methods to create thisPresenter:

class GitRepoMvpPage(context: AppCompatActivity) : SimpleRvPageProtocol, FrameLayout(context) {

    val presenter: Presenter = LifeClean.createPresenter<GithubPresenter, SimpleRvPageProtocol>(context, this)

}
Copy the code

LifeClean. CreatePresenter () by reflection to construct GithubPresenter and call injectLifeOwner (), make GithubPresenter can sense the Activity of the life cycle.

Provide the Activity lifecycle for the View

A View specifically refers to a page implemented using a ViewGroup, but due to multiple inheritance issues, a View is not used in a LifeClean in the same way as a Presenter to sense the lifecycle of an Activity.

First your ViewGroup needs to implement the LifePage interface:

interface LifePage : LifecycleObserver
Copy the code

Then create this ViewGroup using LifeClean’s template methods:

val lifePage = LifeClean.createPage<GitHubLifePage>(activity)
Copy the code

Then you can sense the Activity lifecycle:

class GitHubLifePage(context: AppCompatActivity) : FrameLayout(context),LifePage {

   @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
        Toast.makeText(context, "Received Activity lifecycle event onResume", Toast.LENGTH_SHORT).show()
    }

}
Copy the code

LifeCleanWill be inView DettachAutomatically disconnects whenActivityLife cycle observation.

Timely releaseRxJava Disposable

LifeClean provides a way to automatically release Disposable:

fun Disposable.disposeOnStop(lifeOwner: LifecycleOwner?) : Disposable?Copy the code

For example, release Disposable in LifePresenter:

apiService.searchRepos(query + IN_QUALIFIER, requestPage, PAGE_SIZE) .subscribe({... }) .disposeOnDestroy(getLifeOwner())Copy the code

DisposeOnDestroy (getLifeOwner()) automatically releases the Disposable when LifeOwner Destroy.

specificationRecyclerView.AdapterObject to View mapping

LifeClean recyclerView. Adapter should implement AdapterDataToViewMapping interface, which defines the mapping relationship between object and View:

Interface AdapterDataToViewMapping<T> {Type fun getItemType(data: T): Int // Type -> View fun createItem(type: Int): AdapterItemView<*>?
}
Copy the code

RecyclerView ItemView should implement AdapterItemView interface, so ItemView only needs to get data to do UI rendering:

interface AdapterItemView<T> {
    fun bindData(data: T, position: Int)
}
Copy the code

CommonRvAdapter

CommonRvAdapter is an abstract implementation class of AdapterDataToViewMapping. It requires that all ItemViews should be subclasses of View:

Abstract class CommonRvAdapter<T>(val data: MutableList<T> = ArrayList()) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), AdapterDataToViewMapping<T> { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val item = createItem(viewType) ? : throw RuntimeException("AdapterDataToViewMapping.createItem cannot return null")
        returnCommonViewHolder(item)} // The constructor must be derived from View protected class CommonViewHolder<T> internal constructor(var constructor: AdapterItemView<T>) : RecyclerView.ViewHolder(if (item is View) item else throw RuntimeException("item view must is view"))}Copy the code

The CommonRvAdapter emphasizes mapping objects to Views. Such as:

class SimpleDescView(context: Context) : AppCompatTextView(context), AdapterItemView<SimpleDescInfo> {

    init {
        layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
        setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14f)
        setPadding(30, 30, 30, 0)
        setTextColor(Color.DKGRAY)
    }

    override fun bindData(data: SimpleDescInfo, position: Int) {
        text = data.desc
    }

}
Copy the code

SimpleRvAdapter and MergeAdapter

Both inherit from CommonRvAdapter, which provides the ability to quickly map objects to Views:

val adapter = SimpleRvAdapter<Any>(context).apply {
    registerMapping(String::class.java, SimpleStringView::class.java)
    registerMapping(Repo::class.java, GitRepoView::class.java)
}
Copy the code

The View is dynamically constructed by reflection, but the View must have a constructor(context).

MergeAdapter can combine multiple recyclerView. Adapter that follows AdapterDataToViewMapping interface, it can greatly improve the reuse of recyclerView. Adapter:

  private val titleAdapter by lazy {
        SimpleRvAdapter<Any>(this).apply {
            registerMapping(SimpleTitleInfo::class.java, SimpleTitleView::class.java)
        }
    }

    private val descAdapter by lazy {
        SimpleRvAdapter<Any>(this).apply {
            registerMapping(SimpleDescInfo::class.java, SimpleDescView::class.java)
        }
    }

    private val mergeAdapter by lazy {
        MergeAdapter(
            adapterTitle,
            adapterDesc
        )
    }
Copy the code

The mergeAdapter above combines the mapping capabilities of titleAdapter and descAdapter.

Regulate the refresh of global UI state

The page state of most apps is the same, and LifeClean defines common page states that can be used to standardize the page state refresh logic for the entire App:

Object PageStatus {// Some common page states val START_LOAD_MORE ="start_load_more"
    val END_LOAD_MORE = "end_load_more"
    val START_LOAD_PAGE_DATA = "start_load_page_data"
    val END_LOAD_PAGE_DATA = "end_load_page_data"
    val NO_MORE_DATA = "no_more_data"
    val EMPTY_DATA = "empty_data"
    val NET_ERROR = "net_error"
    val TOAST = "show_toast"
    val PRIVACY_DATA = "privacy_data"
    val CONTENT_DELETE = "content_delete"
    val ERROR = "error"
    val UNDEFINE = "undefine". }Copy the code

End

This article introduces LifeClean’s core ideas and its key features to help you write clear, reusable business code.

Introduction method:

implementation 'com. Susion: life - the clean: 1.0.7'
Copy the code

LifeClean warehouse address:Github.com/SusionSuc/L…