【问题标题】:Can an Android Architecture Components ViewModel compose an object from multiple LiveData returning models?Android 架构组件 ViewModel 可以从多个 LiveData 返回模型组成一个对象吗?
【发布时间】:2018-03-08 03:13:27
【问题描述】:

我无法创建一个 Android 架构组件 ViewModel,它将多个 LiveData 模型组合成一个 LiveData 类以供我的 Fragment 观察。我想对 Fragment 隐藏模型详细信息,并通过各个模型响应外部数据更改。

问题是我需要 ViewModel 来观察模型的变化,但 ViewModel 不是 LifecycleOwner,所以它无法观察。由于我不想将 LiveData 对象传递给 UI,所以我被卡住了。

这可能吗?我是否需要为我的模型放弃 LiveData 并求助于不同的观察模式/工具?

编辑:添加了伪代码。我的实际课程更加复杂和冗长。我希望我的意图是可以理解的。

// OneDataModel.kt
class oneDataModel {
    val oneDataElement = ""
}

// AnotherDataModel.kt
class anotherDataModel {
    val anotherDataElement = 19
}

// OneDataRepository.kt
class OneDataRepository {
    val oneDataSet = MutableLiveData<MutableList<oneDataModel>>()

    private val dataListener = object: ChildEventListener {
        override fun onChildAdded(snapshot: DataSnapshot, p1: String?) {
            val newChild = snapshot.getValue(oneDataModel::class.java)
            if (newChild != null) {
                oneDataSet.value?.add(newChild)
            }
        }
    }

    init {
        oneDataSet.value = mutableListOf<oneDataModel>()
        OneNetworkDataTable.addListener(dataListener)
    }
}

// AnotherDataRepository.kt
class AnotherDataRepository {
    var anotherDataSet = MutableLiveData<MutableList<anotherDataModel>>()

    private val dataListener = object: ChildEventListener {
        override fun onChildAdded(snapshot: DataSnapshot, p1: String?) {
            val newChild = snapshot.getValue(anotherDataModel::class.java)
            if (newChild != null) {
                anotherDataSet.value?.add(newChild)
            }
        }
    }

    init {
        anotherDataSet.value = mutableListOf<anotherDataModel>()
        AnotherNetworkDataTable.addListener(dataListener)
    }
}

// ComposedViewModel.kt
class ComposedViewModel: ViewModel() {
    class ComposedItem {
        var dataName: String = ""   // From OneDataRepository items
        var dataValue: Int = -1     // From AnotherDataRepository items
    }
    var publishedDataSet = MutableLiveData<MutableList<ComposedItem>>()

    //***
    //*** WHAT GOES HERE? HOW DO I LISTEN TO EACH OF THE DATA REPOSITORIES AND BUILD UP COMPOSED
    //*** ITEMS FOR THE UI?
    //***
}

// MyFragment.kt
class MyFragment : Fragment() {
    private val composedViewModel: ComposedViewModel by lazy { ViewModelProviders.of(activity).get(ComposedViewModel::class.java) }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_feed, container, false)

        recyclerView.adapter = UIAdapter

        composedViewModel.publishedDataSet.observe(this, Observer {
            UIAdapter.notifyDataSetChanged()
        })

        return view
    }
}

【问题讨论】:

  • 您能否为您的问题提供一些示例代码?没有代码很难理解。
  • 希望我的想法是正确的。您想要做的事情是删除语句中的 livedata “publishedDataSet”composeViewModel.publishedDataSet.observe(...,...) 以及片段如何具有视图模型并且可以自动观察并更新 UI ?
  • 谢谢@Long Ranger,自从我被卡住后,这就是我在分支上采用的方法。我不喜欢的是我的 ViewModel 基本上是空的,只是传递了 LiveData 存储库成员。这意味着 Fragment 和 Adapter 必须了解数据库模式才能知道从哪里获得他们需要的东西。感觉更像是 MVC 或 MVP,我不认为 AAC 打算这样做。

标签: android android-architecture-components


【解决方案1】:

我建议采取不同的方法:

  • 使用响应式流 (RxJava) 来表示您的模型
  • 使用架构组件中的lifecycle-reactivestreams 将您的 RxJava Observable 转换为 LiveData。

通过使用 RxJava,您可以轻松地将 observable 合并在一起,并且仅使用 LiveData 将该组合公开给视图。我建议仅使用 LiveData 将状态暴露给视图(FragmentActivity)。

我写了一个article 来说明如何做到这一点。您可能会发现它很有用。

【讨论】:

    【解决方案2】:

    希望我的工作能满足你的问题。

    我之前制作了一个示例项目,现在将另一个假实时数据集成到此视图模型中。 Difference of the file

    使用MediatorLiveData 类将多个数据源封装为一个。

    FeedEntryListViewModel.java

    public class FeedEntryListViewModel extends ViewModel {
    
      //list all your live data here
      private LiveData<List<FeedEntry>> feedEntries = new MutableLiveData<>();
      private MutableLiveData<String> userId = new MutableLiveData<>();
      //show your composite model here
      private MediatorLiveData<CompositeModel> compositeModelLiveData;
    
      //list all your repository
      private FeedEntryRepository feedEntryDBRepository;
    
      /*
      The complete model to show the data
       */
      public static class CompositeModel {
    
        String userId = "SystemId";
        private List<FeedEntry> feedEntries = new ArrayList<>();
    
    
        public String getUserId() {
          return userId;
        }
    
        public void setUserId(String userId) {
          this.userId = userId;
        }
    
        public List<FeedEntry> getFeedEntries() {
          return feedEntries;
        }
    
        public void setFeedEntries(
            List<FeedEntry> feedEntries) {
          this.feedEntries = feedEntries;
        }
    
    
      }
    
      public FeedEntryListViewModel(
          FeedEntryRepository feedEntryDBRepository) {
        this.feedEntryDBRepository = feedEntryDBRepository;
        this.feedEntries = feedEntryDBRepository.getAll();
        this.compositeModelLiveData = new MediatorLiveData<>();
        this.compositeModelLiveData.addSource(feedEntries, data ->
        {
          CompositeModel compositeModel = compositeModelLiveData.getValue();
          compositeModel.setFeedEntries(data);
          compositeModelLiveData.postValue(compositeModel);
        });
        this.compositeModelLiveData.addSource(userId, data -> {
          CompositeModel compositeModel = compositeModelLiveData.getValue();
          compositeModel.setUserId(data);
          compositeModelLiveData.postValue(compositeModel);
        });
        //initialize the composite model to avoid NullPointerException
        this.compositeModelLiveData.postValue(new CompositeModel());
      }
    
      public void loadUserId(String userId) {
        this.userId.setValue(userId);
      }
    
      public LiveData<List<FeedEntry>> getFeedEntrys() {
        return feedEntryDBRepository.getAll();
      }
    
      public LiveData<CompositeModel> getCompositeEntrys() {
        return compositeModelLiveData;
      }
    
      public LiveData<List<FeedEntry>> insert(FeedEntry... feedEntries) {
        feedEntryDBRepository.insertAll(feedEntries);
        return feedEntryDBRepository.getAll();
      }
    
      public void delete(FeedEntry feedEntry) {
        feedEntryDBRepository.delete(feedEntry);
      }
    
    
      public int update(FeedEntry feedEntry) {
        return feedEntryDBRepository.update(feedEntry);
      }
    
    }
    

    在Activity中,仍然可以通过语句得到复合

    viewModel.getCompositeEntrys().observe(this, entries -> {...});
    

    View Model Constructor 添加实时数据并绑定到复合实时数据

     public FeedEntryListViewModel(
          FeedEntryRepository feedEntryDBRepository) {
        this.feedEntryDBRepository = feedEntryDBRepository;
        this.feedEntries = feedEntryDBRepository.getAll();
        this.compositeModelLiveData = new MediatorLiveData<>();
        this.compositeModelLiveData.addSource(feedEntries, data ->
        {
          CompositeModel compositeModel = compositeModelLiveData.getValue();
          compositeModel.setFeedEntries(data);
          compositeModelLiveData.postValue(compositeModel);
        });
        this.compositeModelLiveData.addSource(userId, data -> {
          CompositeModel compositeModel = compositeModelLiveData.getValue();
          compositeModel.setUserId(data);
          compositeModelLiveData.postValue(compositeModel);
        });
        //initialize the composite model to avoid NullPointerException
        this.compositeModelLiveData.postValue(new CompositeModel());
      }
    

    【讨论】:

    • 您是否运行过这段代码并看到模型数据进入compositeModel 并由观察者提供?在此过程中,我尝试了与 MediatorLiveData 类似的尝试,但没有成功。您的方法与我记忆中的略有不同,所以我会再试一次。
    • 它正在工作!当我之前尝试过 MediatorLiveData 时,我错了。正如您所展示的,正确的方法是让模型/存储库发布 LiveData 属性,并且 ViewModel 将它们放在 Fragment 可以观察的 MediatorLiveData 属性中。现在我的 Fragment 只看到 ViewModel 并且数据层被适当地隐藏了。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多