Android ViewModel - 在“by viewModels”之前调用“by ActivityViewModels”

Mic*_*l m 3 android mvvm viewmodel kotlin androidx

离开 Android 开发一段时间后,我尝试从一个简单的项目重新开始。

我创建了一个新项目,选择“基本活动”选项,这导致了一个 MainActivity 和两个片段。从这里开始,由于主要功能需要数据库,因此我遵循了“Room with a view”codelab,但它只有一个活动。在我的项目中,我在活动中设置了一个观察者,一切工作正常,但是,一旦我将观察者移动到第一个片段中并使用“by ActivityViewModels”“检索”ViewModel,应用程序就开始抛出实例化异常。原因:MyViewModel 没有零参数构造函数。

经过一些调试后,我注意到片段中的“by ActivityViewModel”属性在活动中的“by viewModel”之前被调用。ViewModel 有一个工厂,我希望它的范围仅限于活动,稍后可以从第二个片段访问。

视图模型:

class MyViewModelFactory(private val repository: MyRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
    if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
        @Suppress("UNCHECKED_CAST")
        return MyViewModel(repository) as T
    }
    throw IllegalArgumentException("Unknown ViewModel class")
  }
}

class MyViewModel(private val repository: MyRepository): ViewModel() {
    val list: LiveData<List<Item>> = repository.allItems.asLiveData()
}
Run Code Online (Sandbox Code Playgroud)

活动

...more imports
import androidx.activity.viewModels

class MainActivity : AppCompatActivity() {

private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding

val myViewModel: MyViewModel by viewModels {
    MyViewModelFactory((application as MyApplication).repository)
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    setSupportActionBar(binding.toolbar)

    val navController = findNavController(R.id.nav_host_fragment_content_main)
    appBarConfiguration = AppBarConfiguration(navController.graph)
    setupActionBarWithNavController(navController, appBarConfiguration)

    myViewModel.list.observe(this) { list ->
        print(list.size)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

分段

...more imports
import androidx.fragment.app.activityViewModels

class ListFragment : Fragment() {

    private var _binding: FragmentListBinding? = null

    private val binding get() = _binding!!


    val sharedViewModel: MyViewModel by activityViewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentListBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        sharedViewModel.list.observe(viewLifecycleOwner) { list ->
            print(list.size)
        }   
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}
Run Code Online (Sandbox Code Playgroud)

依赖关系

 def room_version = "2.4.2"  
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
    implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"

    // Room components
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

    //same result enabling these dependencies
    //implementation 'androidx.activity:activity-ktx:1.4.0'
    //implementation 'androidx.fragment:fragment-ktx:1.4.1'
    //implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
Run Code Online (Sandbox Code Playgroud)

据我了解,活动中的“by viewModels { //factory method }”属性应该使用工厂实例化viewModel,然后片段中的“:viewModelType by ActivityViewModel”属性(没有工厂选项)检索ViewModel定义的类型(如果已由父活动实例化)。

如果我理解正确的话,为什么在“by viewModels”之前调用“by ActivityViewModels”?不应该是相反吗?我该如何修复它?

lad*_*ky0 5

您应该修改您的视图模型、活动和片段。

首先,对于您的 ViewModel,ViewModelProvider.Factory 已被弃用,因此请使用它:

class MyViewModel(application: Application): AndroidViewModel(application) {
    private val repository by lazy { MyRepository.newInstance(application) }
    val list: LiveData<List<Item>> = repository.allItems.asLiveData()
}
Run Code Online (Sandbox Code Playgroud)

然后,在你的活动课中:

class MainActivity : AppCompatActivity() {

   private lateinit var appBarConfiguration: AppBarConfiguration
   private lateinit var binding: ActivityMainBinding

   val myViewModel by viewModels<MyViewModel>()
Run Code Online (Sandbox Code Playgroud)

对于你的片段:

class ListFragment : Fragment() {

    private var _binding: FragmentListBinding? = null

    private val binding get() = _binding!!


    val sharedViewModel by activityViewModels<MyViewModel>()

Run Code Online (Sandbox Code Playgroud)