如何通过Dagger2.2注入具有不同存储库实现的Fragment和ViewModel重用

问题描述 投票:1回答:2

我是Android开发的新手,我一直在寻找一种方法来使用某些Android库(例如Dagger2,Fragments和ViewModel)来执行此模式。

[我希望你们中的一些人可以帮助我解决这个问题,或者说明在Android上执行此操作的常用方法。

我正在寻找这样的东西:

class FragmentPager: Fragment() {

@Inject
@Named("FullListFragment")
lateinit var listFragment: ListFragment

@Inject
@Named("FilteredListFragment")
lateinit var filteredListFragment: ListFragment

//Use fragments in the viewPager. 

}

我正在尝试做的事情:

我有一个片段,显示了元素列表。它还有一个ViewModel负责更新列表。 ViewModel从查询数据库的存储库中获取列表。到目前为止非常简单。

我的用例是,我的应用程序显示应用程序内许多不同区域中的元素列表,但具有不同的数据。例如完整列表,过滤列表...

[我的想法是使用单个方法将存储库创建为接口:fun getItems(): List<Item>和每个数据源的不同实例。结果,我有:

  • ListFragment(从Fragment继承的类)
  • ListViewModel(从ViewModel继承的类)
  • ListRepository(接口)
  • FullListRepository(实现ListRepository的类)->从数据库获取所有项
  • FilterListRepository(实现ListRepository的类)->从数据库中获取过滤器项目
  • JoinedListRepository(实现ListRepository的类)->从数据库中获取项目,但从不同的表中获取项目

这些元素将在这样的理想世界中协同工作:

fun buildListFragment(repository: ListRepository) {
    val viewModel = ListViewModel(repository)
    val fragment = ListFragment(viewModel)
    return fragment
}

val fullListFragment = buildListFragment(FullListRepository())
val filteredListFragment = buildListFragment(FilterListRepository())
val joinedListFragment = buildListFragment(JoinedListRepository())

如何使用Dagger2注入依赖项,使用ViewModelFactory创建ViewModel和片段来执行类似的操作。

我遇到的限制:

  • 我的ViewModel具有参数,因此只能通过ViewModelFactory创建。
  • ViewModel不能由构造方法注入到Fragment中,需要在onCreate中使用viewModelFactory在内部创建。此时,不可能告诉Dagger2应该使用哪个ListRepository实现来创建ViewModelFactory。
class ListFragment: Fragment() {
   @Inject
   lateinit var viewModelFactory: ViewModelProvider.Factory

   override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {

        viewModel = ViewModelProvider(this, viewModelFactory).get(CardListViewModel::class.java)
    }

}
  • 我知道您可以使用@Named批注请求注入不同的存储库实现,但是当在Fragment主体内创建工厂时,请求@Named存储库为时已晚,因为您在创建存储库之前就知道需要哪个存储库片段不在之后。

问题:

  • 使用Dagger2和ViewModels是否可以解决此问题?
  • 如何实现?有什么建议吗?
  • 您通常会使用其他适合同一用例的模式吗?
android-fragments dagger-2 dagger android-viewmodel android-mvvm
2个回答
1
投票

我通常会在发布答案之前先对我的答案进行测试,但是要与先生联系。匕首太过分了。就是说,这是我的有根据的猜测:

具有知道如何提取3个不同数据集(完整,已过滤,已合并)的单个片段意味着需要对其进行参数化。我想可以通过命名注入来完成,但是我只在需要时使用MyFragment.newInstanceA()MyFragment.newInstanceB()等。

在片段中,可能使用了android注入,因为我认为您已经在做,单一的自由格式工厂是通过构造函数注入的,所有实现单一接口的3个存储库。该工厂将包装您的ViewModelProvider.Factory实现,并提供一个方法,例如create,该方法带有实例化片段的参数。

基于参数值,工厂将创建并返回参数正确的ViewModelProvider.Factory实现。然后,视图模型提供者将能够get正确地参数化视图模型。我知道这不是很多匕首,但从理论上讲它应该可以工作:)

PS .:如果数据显然显然来自单个存储,则我不会创建3个不同的存储库。可能需要在视图模型下完成对不同回购方法的调用。


0
投票

我将从这样重新设计存储库开始:

interface ListRepository {

    fun getFullList(): LiveData<List<Product>>

    fun getFilteredList(): LiveData<List<Product>>

    fun getJoinedList(): LiveData<List<Product>>
}

这里假设您要使用房间,则使用LiveData。

然后将我的ViewModel设计为能够根据listType输入获得所需的列表。

class ListViewModel @Inject constructor(
    private val listRepository: ListRepository
) : ViewModel() {

    private val listType = MutableLiveData<String>()

    val productList = Transformations.switchMap(listType) {
        getList(it)
    }

    // call from fragment
    fun fetchList(listType: String) {
        this.listType.value = listType
    }

    private fun getList(listType: String): LiveData<List<Product>> {
        return when (listType) {
            "full" -> listRepository.getFullList()
            "filter" -> listRepository.getFilteredList()
            "joined" -> listRepository.getJoinedList()
            else -> throw IllegalArgumentException("Unknown List Type")
        }
    }
}

switchMap在此处用于防止每次我们从片段中获取列表时,存储库都返回新的LiveData实例。然后是片段将内容包裹起来。

class ListFragment : Fragment() {

    lateinit var listType: String

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private val viewModel: ListViewModel by viewModels { viewModelFactory }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        listType = arguments?.getString("listType")
            ?: throw java.lang.IllegalArgumentException("No listType args found")

        viewModel.fetchList(listType)

        viewModel.productList.observe(viewLifecycleOwner) { products ->
            TODO("render products on recycler view")
        }
    }
}

仅一个片段用于全部三种列表类型,因为我假设这些片段的Ui或多或少相同。将根据传入的listType参数获取并显示列表。

        fragmentManager.beginTransaction()
            .add(R.id.content,ListFragment().apply { 
                arguments = Bundle().apply {
                    putString("listType","full")
                }
            })
            .commitNow()
© www.soinside.com 2019 - 2024. All rights reserved.