MVVM架構(gòu)下Activity和Fragment之間數(shù)據(jù)傳遞和點擊事件回調(diào)的新姿勢

背景

傳統(tǒng)的activity和fragment之間、fragment和fragment之間的數(shù)據(jù)傳遞有以下方式
1、直接使用類似eventbus、livedatabus這類事件總線的方式
2、使用接口回調(diào),找到實現(xiàn)實現(xiàn)該接口的fragment或activity進行強制類型轉(zhuǎn)換再進行方法調(diào)用和參數(shù)傳遞
第一種方式如果項目大了,沒法調(diào)試,代碼可讀性差,管理維護麻煩
第二種方式顯示的持有具有生命周期的fragment或activity理論上都需要套一層WeakReference防止內(nèi)存泄露,麻煩。
今天我們使用livedata+viewmodel的方式解決數(shù)據(jù)傳遞和點擊事件回調(diào),總之兩個字:優(yōu)雅!

實現(xiàn)方式

第一步:依賴引入
app的build.gradle下引入livedata和viewmodel依賴

android {
    //...
    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
dependencies {
    ///...
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel- ktx:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata- ktx:$lifecycle_version"
    // For using KTX Delegates extensions for fragments
    implementation "androidx.fragment:fragment-ktx:$fragment_version"
}

第二步,viewmodel里聲明事件


import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MainViewModel(val data: String) : ViewModel() {

    val showSharedVMToast = MutableLiveData<Unit>()
    
    // Shows a Toast using SharedViewModel implementation
    fun onShowVMToastClick() {
        showSharedVMToast.value = Unit
    }
}

第三步,activity里響應事件

import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer

// Adding the interface that communicates with the fragment
class MainActivity : AppCompatActivity() {

    // Initializing the viewModel on call using KTX-Fragments extension.
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Observing viewModel's LiveData
        viewModel.showSharedVMToast.observe(this, Observer {
            Toast.makeText(this, "Hello from sharedVM", Toast.LENGTH_SHORT).show()
        })

        // Adding the DemoFragment to a container FrameLayout
        supportFragmentManager.beginTransaction().add(R.id.demoFragmentContainer, DemoFragment()).commit()
    }
}

第四步,fragment里調(diào)用

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels

class DemoFragment : Fragment() {

    // We create an instance of MainViewModel using specific KTX-Fragments extension to
    // tell the program that we are willing to use the instance of the Activity this fragment is attached to.
    private val sharedVM: MainViewModel by activityViewModels()

    // We don't need Constructor to pass any extra to this fragment because we have access to the data from SharedVM
    // already.
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_demo, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        //We can tap directly to the viewModel without using Interface callbacks!
        sharedVMButton.setOnClickListener {
            sharedVM.onShowVMToastClick()
        }
    }
}

這樣就完成了fragment里點擊調(diào)用activity事件或數(shù)據(jù)傳遞(反之亦如此),核心就是這兩個東西

    val showSharedVMToast = MutableLiveData<Unit>()
    
    private val sharedVM: MainViewModel by activityViewModels()

如果要傳遞普通的內(nèi)容,如String,Unit換成String即可

擴展

上述點擊事件和數(shù)據(jù)傳遞使用的是liveData,liveData有個特性就是存儲的內(nèi)容會回顯,簡單點說就是fragment被銷毀后,如果對應的viewmodel沒有被銷毀,所持有的livedata對象在fragment/activity被重新創(chuàng)建好后會主動回調(diào)一次,如果業(yè)務邏輯是只想消費一次,如點擊事件,不希望數(shù)據(jù)回顯或數(shù)據(jù)回流,這個時候需要改造liveData成SingleEvent類型,核心思路就是內(nèi)部給一個標記位Event Wrapper,代碼如下:

方式一:內(nèi)部標記位

    class SingleLiveEvent<T> : MutableLiveData<T>() {
    
    private val mPending = AtomicBoolean(false)
    
    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Log.w("SingleLiveEvent", "Multiple observers registered but only one will be notified of changes.")
        }
        // Observe the internal MutableLiveData
        super.observe(owner) { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }
    
    @MainThread
    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }
}
    

使用方式

    //MutableLiveData換成SingleLiveEvent即可
    val mediatorLiveData = SingleLiveEvent<String>()

方式二:Event Wrappter

    /**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event<out T>(private val content: T) {

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
}

使用方式

class ListViewModel : ViewModel {
    private val _navigateToDetails = MutableLiveData<Event<String>>()

    val navigateToDetails : LiveData<Event<String>>
        get() = _navigateToDetails


    fun userClicksOnButton(itemId: String) {
        _navigateToDetails.value = Event(itemId)  // Trigger the event by setting a new Event as a new value
    }
}

觀察的地方

myViewModel.navigateToDetails.observe(this, Observer {
    it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
        startActivity(DetailsActivity...)
    }
})
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容