How to fix 'Cannot create an instance of class....'? - android

Here is my build.gradle
dependencies {
//viewmodel
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
//noinspection GradleDependency
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation 'androidx.activity:activity-ktx:1.4.0'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
}
viewModel
class AlarmViewModel(application: Application) : AndroidViewModel(application) {
private var repository : AlarmRepository = AlarmRepository(application)
var list : LiveData<List<Alarm>> = repository.list
}
Fragment
class AlarmListFragment : Fragment() {
private lateinit var viewModel: AlarmViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentAlarmListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity(), ViewModelProvider.AndroidViewModelFactory.getInstance(activity!!.application)).get(AlarmViewModel::class.java)
}
}
}
Searching on google and trying to fix but it doesn't work
plz help me :<
I need ur helpe!!!

Related

Android ViewModel - "by activityViewModels" called before "by viewModels"

After some time away from android development I'm trying to start again with a simple project.
I've created a new project picking the "basic activity" option which resulted in a MainActivity and two fragments. Starting from this, since the main functionality requires a database, I've followed the "Room with a view" codelab, which however has a single activity. In my project I set an observer in the activity and all worked fine but, as soon as I moved the observer in the first fragment and "retrieved" the ViewModel with "by activityViewModels", the app started throwing an Instantiation exception. Reason: MyViewModel has no zero argument constructor.
After some debugging, I've noticed that the "by activityViewModel" property in the fragment is called before the "by viewModel" in the activity.
The ViewModel has a factory and I would like it scoped to the activity and later would be accessed from the second fragment.
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()
}
Activity
...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)
}
}
}
Fragment
...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
}
}
Dependencies
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'
For what I understand, the "by viewModels { //factory method }" property in the activity should instantiate the viewModel using the factory, then the ":viewModelType by activityViewModel" property in the fragment (which has no factory option) retrieve a ViewModel of the defined type, if already instantiated by the parent activity.
If I have understood correctly, why "by activityViewModels" is called before "by viewModels"? Shouldn't be the other way around? How can I fix it?
You should modify your viewmodel, activity and fragment.
First, for your ViewModel, the ViewModelProvider.Factory is deprecated, so use this instead :
class MyViewModel(application: Application): AndroidViewModel(application) {
private val repository by lazy { MyRepository.newInstance(application) }
val list: LiveData<List<Item>> = repository.allItems.asLiveData()
}
Then, in your activity class:
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
val myViewModel by viewModels<MyViewModel>()
And for your fragment:
class ListFragment : Fragment() {
private var _binding: FragmentListBinding? = null
private val binding get() = _binding!!
val sharedViewModel by activityViewModels<MyViewModel>()

onOptionsItemSelected access the dataBinding viewModel

How to let the dataBinding viewModel accessible on onOptionsItemSelected method.
class TestFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// viewModel
val viewModelFactory = TestViewModelFactory(
...
)
val viewModel = ViewModelProvider(
this, viewModelFactory).get(TestViewModel::class.java)
// dataBinding
val binding = FragmentTestBinding.inflate(inflater)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId) {
R.id.test_menu_item -> {
this.findNavController().navigate(
TestFragmentDirections
.actionTestFragmentToAnotherTestFragment(
...
)
)
viewModel.onNavigated() // How to access the viewModel here
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
Ok, declare lateinit global variable which is accessable for whole class
private lateinit var viewModel : TestViewModel
Now initialize viewModel with factory in onCreate():
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// factory
val viewModelFactory = TestViewModelFactory()
// viewModel
viewModel = ViewModelProvider(this, viewModelFactory).get(TestViewModel::class.java)
}
So the whole code looks like :
class TestFragment : Fragment() {
private lateinit var viewModel : TestViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// factory
val viewModelFactory = TestViewModelFactory()
// viewModel
viewModel = ViewModelProvider(this, viewModelFactory).get(TestViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// dataBinding
val binding = FragmentTestBinding.inflate(inflater)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId) {
R.id.test_menu_item -> {
this.findNavController().navigate(
TestFragmentDirections.actionTestFragmentToAnotherTestFragment()
)
viewModel.onNavigated() // How to access the viewModel here
true
}
else -> super.onOptionsItemSelected(item)
}
}
}

issue with fragment-to-fragment communication using viewModel

I'm new to Kotlin and android development but i can't find why my program isn't working.
I'm trying to be able to communicate from my first fragment to his child, and testing it with a string but it won't display.
Thanks in advance for your help !!!
My first fragment :
class FirstFragment : Fragment() {
private lateinit var viewModel : Communicator
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activity?.run {
ViewModelProvider(this).get(Communicator::class.java) // .of supprimé
} ?: throw Exception("Invalid Activity")
viewModel.message.value = "test"
view.findViewById<Button>(R.id.button_stall_selection).setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
}
}
Here is my second :
class SecondFragment() : Fragment() {
private lateinit var viewModel :Communicator
private var msg: String? = ""
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activity?.run {
ViewModelProvider(this).get(Communicator::class.java) // .of deleted
} ?: throw Exception("Invalid Activity")
viewModel.message.observe(viewLifecycleOwner, Observer {
msg = viewModel.message.value
})
view.findViewById<TextView>(R.id.textView_1).text = msg
view.findViewById<Button>(R.id.button_second).setOnClickListener {
findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
}
}
}
and finally here is the viewModel class i'm trying to use in order to communicate :
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class Communicator() : ViewModel(){
val message =MutableLiveData<String>()
fun setMsgCommunicator(msg:String){
message.setValue(msg)
}
}
In your FirstFragment, try to call viewModel.setMsgCommunicator("test") instead of directly calling viewModel.message.value = "test"

Where should I place viewDataBinding.lifecycleOwner = this.viewLifecycleOwner with Data Binding and Lifecycle?

The following code is from the project architecture-samples, you can see it here.
I'm not sure where I should place viewDataBinding.lifecycleOwner = this.viewLifecycleOwner between onCreateView() and onActivityCreated(), could you tell me?
class TasksFragment : Fragment() {
private lateinit var viewDataBinding: TasksFragBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewDataBinding = TasksFragBinding.inflate(inflater, container, false).apply {
viewmodel = viewModel
}
setHasOptionsMenu(true)
//viewDataBinding.lifecycleOwner = this.viewLifecycleOwner Can I place here?
return viewDataBinding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Set the lifecycle owner to the lifecycle of the view
viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
}
..
}
onActivityCreated is deprecated. You should use onViewCreated or onCreateView.
private var binding: TasksFragBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val viewDataBinding = TasksFragBinding.inflate(inflater, container, false).apply {
viewmodel = viewModel
lifecycleOwner = viewLifecycleOwner
}
this.binding = viewDataBinding
return viewDataBinding.root
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}

How to generify data binding layout inflation?

I wanna make a BaseFragment. For this, I have to use ViewDataBinding and ViewModel. using generic, I can use variable but not static field. For example I have to Inflate writing this code "FragmentSecondBinding.inflate(layoutInflater, container, false) ". So I tried this code "T.inflate(layoutInflater, container, false)" but got some error. Also ViewModel is like this.
How can I make this code to BaseCode?
abstract class BaseFragment<T: ViewDataBinding, M : ViewModel> : DaggerFragment(){
abstract val layoutId : T
private lateinit var binding : T
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val viewModel by viewModels<M> { viewModelFactory }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = T.inflate(inflater, container, false).apply {
viewmodel = viewModel
}
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.lifecycleOwner = this.viewLifecycleOwner
}
There is a way to abstract out the particular ViewDataBinding, however, it would require to provide a layout resource reference for each concrete fragment implementation:
protected abstract val layoutResource: Int
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val viewModel by viewModels<M> { viewModelFactory }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, layoutResource, container, false).apply {
viewmodel = viewModel
}
return binding.root
}

Categories

Resources