Blog (1/4) is just a summary of the article accidentally found by you. If you are interested, the project address is at the end of the article

1. What is that?

MVP is a design pattern (framework) that is widely used in Android projects because of its excellent decoupling capabilities. It divides applications into Model-View-Presenter, which is called MVP for short

  • The Model handles and stores the data and calls back to the Presenter
  • The Presenter is responsible for forwarding requests from the View layer (such as clicking to update the View data) to the corresponding Model, receiving the callback and notifying the View layer to update the View
  • The View is only responsible for displaying data
  • The Contract class is only used to define the interface to the View and Model for easy management and viewing

A simple basic process for updating a view

















Some versions of the MVP might choose to put the data processing in Presenter, and then the Model would have a setter/getter javabean-like function, but I think that makes Presenter bloated, so I choose to put the logical processing in the Model. You can square root either way

2. MVP general framework

2.1 Contract layer

Contract does not have a very general framework because each view and model work differently. Here is the example shown above

class DetailContract{
    interface DetailView{
        fun onChangeText(String)
    }

    interface DetailModel {
        fun getNowText(callBack: GetTextCallBack)
    }
    interface GetTextCallBack{
        fun onSuccess(str:String)
        fun onFail(info:String)}}Copy the code

2.2 Model layer

There is no universal framework for the Model. Here is the example shown above

class SampleModel: DetailContract.DetailModel{
    override getNowText(callBack: GetTextCallBack){
        val str = ...
        // This is the String operation
        if(str! =""){
            callBack.onSuccess(str)   
        }else{
            callBake.onFail("Fetch failed")}}}Copy the code

The concrete Model class implements the interface in the Contract class that we Presenter call

2.3 the View layer

Views in Android generally include two types, one is Activity, and the other is Fragment. Here, only the encapsulation of Activity is given. Fragment is similar and needs to deal with some life cycle problems.

Activity:

abstract class BaseActivity<V,T:BasePresenter<V> > :Activity() {val TAG:String = javaClass.simpleName

    protected lateinit var mPresenter: T

    lateinit var mContext: Context

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        mContext = this
        // Initialize Presenter
        mPresenter = createPresenter()
        // Bind Presenter to View
        mPresenter.attachView(this as V)
        // Initialize the layout
        initView(savedInstanceState)
    }

    /** * the view initialization method that should be implemented by subclasses */
    abstract fun initView(savedInstanceState: Bundle?).

    /** * Create the corresponding Presenter */
    abstract fun createPresenter(a):T

    // Unbind
    override fun onDestroy(a) {
        super.onDestroy()
        mPresenter.detachView()
    }
}
Copy the code

BaseActivity is an abstract class that all activities added to MVP mode should inherit from. The generic V represents the view (itself) and T is the Presenter. The View layer holds a reference to the Presenter used to send messages.

2.4 the Presenter layer

abstract class BasePresenter<T> {
    // A weak reference to the View interface type, which prevents the View being held by the presenter from being destroyed, resulting in memory leaks
    protected lateinit var mViewRef:Reference<T>

    // Bind the View reference
    fun attachView(view:T){
        mViewRef = SoftReference<T>(view)
    }

    // Get the currently bound View reference
    protected fun getView(a): T? {
        return mViewRef.get()}// Whether the View is bound
    fun isViewAttached(a): Boolean {
        returnmViewRef ! =null&&mViewRef.get()! =null
    }

    // Dereference
    fun detachView(a){
        if(mViewRef ! =null){
            mViewRef.clear()
        }
    }
}
Copy the code

BasePresenter is an abstract class that should be inherited by all presenters in MVP mode. Presenter holds a weak reference to the View layer. There are four methods associated with weak references: bind a reference to a View, get a reference to the current View, determine if the View is bound to a View, and remove a reference from the View. There is also a Model object in a specific Presenter. In other words, a Presenter holds both a View and a Model so that information can be forwarded

The View interface type in the Contract is passed in because it allows the Presenter to transmit information only to the View through the interface. Rather than a specific type.

These are some of the commonly used frameworks. Here we use the actual combat to continue to deepen our understanding:

3. The actual combat

This example is selected from the mid-term assessment of Hongyan Mobile Development Department, and the content is a music App. Just analyze the play page (because I only made two pages 😭)

The home page Play page

The main function is to play the scrolling of music and lyrics. Let’s look at the structure first:

1. The Contract

The first layer I think I should write is this one, which regulates the specific behavior of our View and Model: DetailMusicContract:

class DetailMusicContract{
    interface DetailView{
        fun showDetailMusic(name:String,author:String,imageUrl:String)
        fun showLyric(viewList:ArrayList<View>)
        fun showToast(message:String)
        fun changeLyricPosition(position:Int)
        fun changeNowTimeTextView(time:String)
        fun changeSeekBarPosition(position:Int)
    }

    interface DetailModel {
        fun getNowMusic(callBack: GetNowMusicCallBack)
        fun getLyric(context:Context,callBack: GetLyricCallBack)
    }
    interface GetNowMusicCallBack{
        fun onSuccess(music: MyMusic)
        fun onFail(info:String)
    }
    interface GetLyricCallBack{
        fun onSuccess(viewList: ArrayList<View>)
        fun onFail(info:String)}}Copy the code

There are six methods defined in the Interface DetailView

  • showDetailMuiscDisplay the name, author, and picture of the current song
  • showLyricUsed to display lyrics (initialize ViewPager and Adapter)
  • changeLyricPositionUsed to change the position of the current lyrics (i.e. lyrics rotation)
  • changNowTimeTextViewUsed to change the playing time of the current song
  • changeSeekBarPositionTo change the progress of the slider

Two methods are defined in the Interface DetailModel

  • getNowMusicUsed to get the currently playing music from the Service that manages music playback
  • getLyricUsed to get the lyrics of the currently playing music

Tips: In many cases, Model methods are added later, because you may not know what methods are required by the Model at first

2. The View layer

BaseActivity has been shown before, but actually BaseActivity adds something about service binding that is outside the scope of this article DetailMusicActivity:

class DetailMusicActivity : BaseActivity<DetailMusicContract.DetailView, DetailMusicPresenter>(),
        DetailMusicContract.DetailView,
        MyMusicPlayerManager.OnStartPlay,
        MyMusicPlayerManager.StartNextMusic,
        View.OnClickListener{
    override fun initView(savedInstanceState: Bundle?). {
        setContentView(R.layout.activity_detail)
        iv_detail_play.setOnClickListener(this)
        iv_detail_previous.setOnClickListener(this)
        iv_detail_next.setOnClickListener(this)
        iv_detail_back.setOnClickListener(this)
        // The music is ready for the callback
        MyMusicPlayerManager.instance.setOnStartPlay(this)
        MyMusicPlayerManager.instance.setStartNextMusic(this)}// Implement music data after binding successfully
    override fun onService(name: ComponentName? , service:IBinder?). {
        Toast.makeText(this."Binding successful",Toast.LENGTH_SHORT).show()
        changeNowMusic()
    }

    override fun createPresenter(a): DetailMusicPresenter {
        return DetailMusicPresenter()
    }

    override fun showDetailMusic(name: String, author: String, imageUrl: String) {
        tv_detail_name.text = name
        tv_detail_author.text = author
        ImageLoader.with(this)
                .from(imageUrl)
                .disposeWith(CutToCircle())
                .cacheWith(DoubleCacheUtils.getInstance())
                .into(iv_detail_music)
    }

    // It is necessary to change the music. Note that you can do some operations that have not yet been retrieved but are already available
    override fun changeNowMusic(a) {
        Log.d("Refresh the music"."")
        mPresenter.getNowMusic()
        mPresenter.getLyric(this)
        mPresenter.startToChangeTextView()
        mPresenter.startToChangeSeekBar()
        sb_detail.max = MyMusicPlayerManager.instance.musicDuration()
        sb_detail.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
            var isTouch = false
            override fun onProgressChanged(seekBar: SeekBar? , progress:Int, fromUser: Boolean) {
                if (isTouch){
                    valposition = seekBar!! .progress MyMusicPlayerManager.instance.musicSeekTo(position) mPresenter.pause() } }override fun onStartTrackingTouch(seekBar: SeekBar?). {
                isTouch = true
            }

            override fun onStopTrackingTouch(seekBar: SeekBar?). {
                isTouch = false
                MyMusicPlayerManager.instance.play()
            }

        })
    }
    // Trigger the callback to display the lyrics. Note that there should be UI operations that can only be done once the lyrics have been retrieved
    override fun showLyric(viewList:ArrayList<View>) {
        Log.d("Lyrics display callback"."Success")
        runOnUiThread {
            val adapter = MyViewPagerAdapter(viewList)
            mb_lyric.init()
            mb_lyric.setScrollTime(1500)
            mb_lyric.setAdapter(adapter,this)
            mb_lyric.setTransformer(CustomTransformer())
            mPresenter.startToChangeLyric()
        }
    }

    override fun onNextMusic(a) {
        mPresenter.playNext()
    }

    override fun showToast(message:String) {
        Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
    }

    override fun changeLyricPosition(position: Int) {
        runOnUiThread {
            mb_lyric.changeTo(position)
        }
    }

    override fun changeNowTimeTextView(time: String) {
        runOnUiThread{
            tv_detail_now.text = time
        }
    }

    override fun changeSeekBarPosition(position: Int) {
        runOnUiThread {
            sb_detail.progress = position
        }
    }

    // Click on the centralized processing of events
    override fun onClick(v: View?). {
        when{ v!! .id == iv_detail_play.id -> {if (MyMusicPlayerManager.instance.isPlaying()){
                    mPresenter.pause()
                }else{
                    mPresenter.play()
                }
            }
            v.id == iv_detail_previous.id -> {
                mPresenter.playPrevious()
            }
            v.id == iv_detail_next.id -> {
                mPresenter.playNext()
            }
            v.id == iv_detail_back.id -> {
                this.finish()
            }
        }
    }

    /** * Life cycle related */

    override fun onDestroy(a) {
        super.onDestroy()
        mPresenter.cancelTimer()
    }
}
Copy the code

The code may seem a bit long because the page is relatively complex, but the structure of the View is clear.

  • override fun initView

    This method is inherited from BaseActivity and is used to initialize the layout after the first startup. You can see that we have set up some control listener and callback interface here. It needs to be explainedMyMusicPlayerManager.instance.setOnStartPlay(this) MyMusicPlayerManager.instance.setStartNextMusic(this)These two methods need to be set up because the music player needs to load the music playback data asynchronously from the networkCallback interface for preparing music for playbackAs well asAfter playing, switch to the callback interface of the next trackTheir corresponding method is

    override fun changeNowMusic()The callback triggered when the current piece of music changesoverride fun onNextMusic()Switch to the callback interface of the next track after playing
  • override fun onService

    This method is inherited from the BaseActivity method (the above BaseActivity method was not added, but because this is a music App, it needs to be bound to the current Activity). Here we perform the action after the binding operation is completed: executechangeNowMusic()To initialize the music interface
  • Override fun createPresenter inherits the BaseActivity method and creates a corresponding Presenter instance
  • Override Fun showDetailMusic This is a method defined in the Contract interface that displays the values of some controls
  • override fun changeNowMusic

    MyMusicPlayerManager.OnStartPlayA method that displays all information about the currently playing song and sends four messages to Presenter.getNowMusicTo request the display of current music,getLyricUsed to request lyrics,startToChangeTextViewUsed to request to start constantly updating the current play time,startToChangeSeekBarUsed to request continuous updates to SeekBar progress. Then set up some SeekBar listeners to control the progress of the music
  • Override Fun showLyric This is a method defined in the Contract interface that displays lyrics, and at the end sends a message to Presenter asking to start scrolling the lyrics screen
  • override fun onNextMusic

    This is defined inMyMusicPlayerManager.StartNextMusicThe method in the interface, as mentioned above, is a callback that starts after the music has automatically played, ShowToast, changeLyricPosition, changeNowTimeTextView, changeSeekBarPosition, and the onClick control for centralized processing

The View layer provides a variety of interfaces to call back to the Presenter and the music service based on the user’s actions

3. The Presenter layer

DetailMusicPresenter:

class DetailMusicPresenter : BasePresenter<DetailMusicContract.DetailView>() {private var lyricTimer:Timer = Timer()
    private var textViewTimer:Timer = Timer()
    private var seekBarTimer:Timer = Timer()
    private val detailMusicModel = DetailMusicModel()
    // Gets the callback of the currently playing music
    fun getNowMusic(a){
        detailMusicModel.getNowMusic(object :DetailMusicContract.GetNowMusicCallBack{
            override fun onSuccess(music: MyMusic) {
                mViewRef.get()!!!!! .showDetailMusic(music.name,music.author,music.imageUrl) }override fun onFail(info: String) {
                mViewRef.get()!!!!! .showToast(info) } }) }fun getLyric(context: Context){
        detailMusicModel.getLyric(context,object :DetailMusicContract.GetLyricCallBack{
            override fun onSuccess(viewList:ArrayList<View>) {
                mViewRef.get()!!!!! .showLyric(viewList) }override fun onFail(info: String) {
                mViewRef.get()!!!!! .showToast(info) } }) }fun startToChangeLyric(a){
        lyricTimer = Timer()
        lyricTimer.schedule(object : TimerTask() {
            override fun run(a) {
                mViewRef.get()!!!!! .changeLyricPosition(MyMusicPlayerManager.instance.getNowLyricPosition()) } } ,0.100)}fun startToChangeTextView(a){
        textViewTimer = Timer()
        textViewTimer.schedule(object : TimerTask(){
            override fun run(a) {
                mViewRef.get()!!!!! .changeNowTimeTextView(MyMusicPlayerManager.instance.nowTimeInMin()) } },0.100)}fun startToChangeSeekBar(a){
        seekBarTimer = Timer()
        seekBarTimer.schedule(object :TimerTask(){
            override fun run(a) {
                mViewRef.get()!!!!! .changeSeekBarPosition(MyMusicPlayerManager.instance.musicCurrent()) } },0.100)}// Music control
    fun play(a){
        MyMusicPlayerManager.instance.play()
    }
    fun pause(a){
        MyMusicPlayerManager.instance.pause()
    }
    fun playPrevious(a){
        cancelTimer()
        MyMusicPlayerManager.instance.playPrevious()
    }
    fun playNext(a){
        cancelTimer()
        MyMusicPlayerManager.instance.playNext()
    }
    fun cancelTimer(a){
        lyricTimer.cancel()
        textViewTimer.cancel()
        seekBarTimer.cancel()
    }
}
Copy the code

Because Presenter’s function is to forward, the code is not long and the structure is clear

  • First, you have a DetailModel instance that doesn’t require much elaboration
  • getNowMusic

    This is called in the View layer to retrieve information about the current music. The code is not too difficult to understand. If it succeeds, it is called by the Presenter’s View layer reference as defined in the ContractshowDetailMusicMethod to notify the View layer of an update, and if it fails to do so call it as previously defined in the ContractshowToastMethod to display a Toast message to remind the user
  • fun getLyric

    This is also called in the View layer, used to get the lyrics of the current music, successful call back to the View layer interface to display the lyrics, otherwise display Toast
  • fun startToChangeLyricfun startToChangeTextView,fun startToChangeSeekBarAlso defined in the View layer, the implementation is almost the same, start a new TimerTask, periodically get the current lyrics should be in the position, the current has been playing time, the current SeekBar should be in the progress, and then call the View layer corresponding method to update
  • No need to say more about the music control, but note that you need to cancel the Timer before the next track, otherwise an error will appear
  • fun cancelTimer

    When the View is destroyed or the song is changed, Lyric is not initialized or nullPoint fails because the Timer is executed in the child thread

4. The Model layer

DetailMusic:

class DetailMusicModel: DetailMusicContract.DetailModel {
    override fun getNowMusic(callBack: DetailMusicContract.GetNowMusicCallBack){
        val music = MyMusicPlayerManager.instance.nowMusic()
        callBack.onSuccess(music)
    }

    override fun getLyric(context:Context,callBack: DetailMusicContract.GetLyricCallBack) {
        val music = MyMusicPlayerManager.instance.nowMusic()
        val request = Request.Builder("http://elf.egos.hosigus.com/music/lyric?id=${music.id}")
                .setMethod("GET").build()
        NetUtil.getInstance().execute(request,object :Callback{
            override fun onResponse(response: String?). {
                val mainJson = JSONObject(response)
                val str = mainJson.getJSONObject("lrc").getString("lyric")
                val lyric = Lyric(str!!)
                MyMusicPlayerManager.instance.nowMusic().lyric = lyric
                val viewList = ArrayList<View>()
                for (i in 0 until lyric.arrayList.size){
                    val view = LayoutInflater.from(context).inflate(R.layout.item_lyric,null)
                    view.findViewById<TextView>(R.id.tv_item_lyric).text=lyric.arrayList[i]
                    viewList.add(view)
                }
                callBack.onSuccess(viewList)
            }
            override fun onFailed(t: Throwable?).{}})}}Copy the code

The Model class is primarily used to collect data and provide it to presenters

  • override fun getNowMusic

    Gets the current music and calls back to Presenter, who, upon receiving the callback, notifies the View layer to update it
  • override fun getLyric

    Get the current lyrics and package them as an ArrayList callback to Presenter, who receives the callback and notifies the View layer to update it

conclusion

This article is only for the structure of the MVP analysis, some other content, such as music player, custom lyrics View, etc., are not involved, if interested, you can visit GitHub source address

Other

Sorry to drag down the quality of nuggets articles… I technology is limited, still in learning, if there is anything wrong place hope big men correct!! I wrote it as a summary and didn’t think how many people would read HHHHHH 🤣