Persist states after viewModelLifecyle till the app get closed - android

I'm a beginner android developer and I got stuck with an issue.
viewModel keeps the states change data related to a fragment and when the user navigate to some fragment then the viewModel no longer keeps the states.
What I want is that when the user change something in the fragment and after that navigate to other fragment and then come back to the fragment where he changes something, he should see the changes he made.
Solutions are there for storing ex. shared_preference & Room, but my requirement is that I only want to keep the state data alive until the use closed the App.
I've googling so far but still didn't understand the approach developers follow to do these things. So can you also provide some guidance on approaches I should follow that consider the best practices. My main motive is to learn.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="homeViewModel"
type="com.android.example.presentation.home.HomeViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".presentation.home.HomeFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<TextView
android:id="#+id/sampleText"
android:text="#{String.valueOf(homeViewModel.sampleText)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
<Button
android:id="#+id/sampleButton"
android:onClick="#{() -> homeViewModel.samplePressed()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/sampleText" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</layout>
class HomeViewModel : ViewModel() {
private val _sampleText = MutableLiveData(0)
val sampleText : LiveData<Int>
get() = _sampleText
fun samplePressed() {
_sampleText.postValue(1)
}
}

Thanks to the comment from Vivek Gupta
This can be achieved by scoping the ViewModel to the activity's lifecycle
previous
class HomeFragment : Fragment() {
private val viewModel: HomeViewModel by viewModels()
}
Now change it this
class HomeFragment : Fragment() {
private val viewModel: HomeViewModel by activityViewModels()
}
Edit: another way to do this if we have viewModelFactory
class HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding
private lateinit var mainRepository: MainRepository
private lateinit var viewModel: HomeViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) : View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainRepository = MainRepository(RetrofitInstance.api)
val viewModelFactory = HomeViewModelFactory(mainRepository)
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(HomeViewModel::class.java)
binding.homeViewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
}
}

Related

On Click event Does not working with Data Binding in Android Studio

I am learning DataBinding in android studio. But I am facing a problem with binding a ModelView. I want to bind a function with a button on click event. I set a function in the model view. I want to update my text view with on click event of my button. But When I click the button my text is not updating. I can not understand what I have done wrong.
XML layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="model"
type="com.example.jetpack.MainModelView" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{model.title}"
android:textSize="28sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Update Text"
android:onClick="#{()-> model.update()}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tv" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Main Model View:
class MainModelView : ViewModel() {
var title: String = " This is My Application"
fun update() {
title = "I am Changed"
Log.d("UPDATE", "update successfully from main model view")
}
}
Main Activity:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
lateinit var mainModelView: MainModelView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mainModelView = ViewModelProvider(this).get(MainModelView::class.java)
binding.model = mainModelView
}
}
My app Image:
thanks in advance for helping.
The title variable in ViewModel needs to be a ObservableField or LiveData otherwise you xml will never know when it's value got updated –
class MainViewModel : ViewModel() {
var text = MutableLiveData(" Welcome to my application ")
fun updateText() {
text.value = " Text is updated successfully "
}
}
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// Creating MainViewModel object
mainViewModel = MainViewModel()
// Binding mainViewModel variable
// with MainViewModel object
binding.mainViewModel = mainViewModel
// passing LifeCycleOwner to binding object
binding.lifecycleOwner = this
}
}

Room livedata , databinding issue : UI doesn't update when data changes

I am now developing an application where I use MVVM pattern for UI and repository interaction. In other words, I receive live data object with a list of models from my ROOM data base query via repository, then assign it to my live data variable in viewmodel. After that, this data should be populated to my xml layout recycler view via data binding, but It happens only once fragment is initialised. In other cases recycler view is void
DAO code :
#Query("SELECT * FROM note WHERE content LIKE :query ORDER BY isStarred")
fun searchAllNotes(query : String?) : Flow<List<Note>>
ViewModel code:
#HiltViewModel
class HomeViewModel #Inject constructor (
private val noteRepository : NoteRepository
) : ViewModel() {
private var _notesLiveData : LiveData<List<Note>> = noteRepository.getAllNotes().asLiveData()
val notesLiveData get()= _notesLiveData
fun searchNotes(query : String){
viewModelScope.launch(Dispatchers.IO){
_notesLiveData = noteRepository.searchAllNotes(query).asLiveData()
}
}
fun deleteNote(note : Note){
viewModelScope.launch(Dispatchers.IO){
noteRepository.deleteNote(note)
}
}
fun updateNoteChecked(note : Note){
viewModelScope.launch(Dispatchers.IO){
noteRepository.updateNoteChecked(note.id, note.isStarred)
}
}
Fragment code
#AndroidEntryPoint
class HomeFragment : Fragment(),
NoteCardAdapter.NoteTouchListener,
SearchView.OnQueryTextListener{
private var _binding : FragmentHomeBinding? = null
val binding get() = _binding!!
private val adapter by lazy {
NoteCardAdapter(this as NoteCardAdapter.NoteTouchListener)
}
private val vm : HomeViewModel by viewModels()
private val noteSharedViewModel : NoteSharedViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.adapter = adapter
binding.vm = vm
binding.apply {
lifecycleOwner = viewLifecycleOwner
homeRecyclerView.isNestedScrollingEnabled = false
homeRecyclerView.layoutManager = LinearLayoutManager(view.context)
homeRecyclerView.itemAnimator= NotesItemAnimator()
}
binding.homeSearchView.isSubmitButtonEnabled = true
binding.homeSearchView.setOnQueryTextListener(this as SearchView.OnQueryTextListener)
binding.addNoteButton.setOnClickListener{
val note = Note(
"","","",false, activity?.getDate(), folderId = -1
)
noteSharedViewModel.selectNote(note)
val action = HomeFragmentDirections.actionHomeFragmentToSingleNoteFragment(
isNew = true
)
findNavController().navigate(action)
}
binding.foldersButton.setOnClickListener{
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToFoldersFragment())
}
}
xml layout code :
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
>
<data>
<variable
name="vm"
type="com.example.leonidsnotesapplication.presentation.notes_feature.viewmodels.HomeViewModel" />
<variable
name="adapter"
type="com.example.leonidsnotesapplication.presentation.notes_feature.adapters.NoteCardAdapter" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="#color/note_background_color_3"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".presentation.notes_feature.fragments.HomeFragment">
<androidx.appcompat.widget.SearchView
android:id="#+id/homeSearchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:theme="#style/AppSearchView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="#+id/tvRecentTitle"
android:textStyle="bold"
android:textSize="25sp"
android:textColor="#color/button_color_2"
android:text="#string/recent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
app:layout_constraintTop_toBottomOf="#id/homeSearchView"
app:layout_constraintStart_toStartOf="parent"/>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="28dp"
android:background="#drawable/ic_circle_arrow"
app:layout_constraintStart_toEndOf="#id/tvRecentTitle"
app:layout_constraintTop_toBottomOf="#id/homeSearchView" />
<androidx.recyclerview.widget.RecyclerView
tools:listitem="#layout/note_card_view"
android:scrollbars="vertical"
android:scrollbarStyle="outsideInset"
android:id="#+id/homeRecyclerView"
android:layout_width="match_parent"
android:requiresFadingEdge="vertical"
android:fadingEdge="vertical"
android:fadingEdgeLength="15dp"
android:layout_height="500dp"
android:layout_marginTop="30dp"
app:setNoteAdapter="#{adapter}"
app:submitNoteList="#{vm.notesLiveData}"
app:layout_constraintTop_toBottomOf="#id/tvRecentTitle"
app:layout_constraintStart_toStartOf="parent"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:backgroundTint="#color/button_color_1"
android:id="#+id/add_note_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="#drawable/ic_create"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:backgroundTint="#color/button_color_1"
android:id="#+id/folders_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="#drawable/ic_folders_stack"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Data binding adapter :
#BindingAdapter("submitNoteList")
fun submitNoteList(recyclerView: RecyclerView, data : List<Note>?){
val adapter = recyclerView.adapter as NoteCardAdapter
adapter.setData((data as ArrayList<Note>? ?: arrayListOf()))
}
#BindingAdapter("setNoteAdapter")
fun setNoteAdapter(recyclerView: RecyclerView, adapter: NoteCardAdapter){
adapter.let {
recyclerView.adapter = it
}
}
You should observe your live data. Example code is:
vm.notesLiveData.observe(viewLifecycleOwner) { adapter.submitList(it) }

Using data bindings in Fragments

I have a problem using viewmodel in fragment with data binding.
In my programm i'm using only one activity and fragments. I organized transitions between fragments using navigation. And i want to use data binding in my fragment, but when running the application only with binding, everything works well when I pass to the binding viewmodel when switching to this fragment of the application, it crashes.
I would be very grateful for your help!
My fragment
class TimeSettings : Fragment() {
private lateinit var viewModel: TimeSettingsViewModel
private lateinit var binding: FragmentTimeSettingsBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentTimeSettingsBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
binding.viewmodel = viewModel;
return binding.root
}
}
Object for viewmodel
object TimeData {
var inhale: Int = 4
private val _currentTimeInhale = MutableLiveData<Int>()
val currentTimeInhale: LiveData<Int>
get() = _currentTimeInhale
init{
_currentTimeInhale.value = inhale
}
fun changeCurrentTimeInhale(){
_currentTimeInhale.value = 5
}
}
Viewmodel
class TimeSettingsViewModel : ViewModel() {
val currentInhale: LiveData<Int>
get() = TimeData.currentTimeInhale
fun onChange() = TimeData.changeCurrentTimeInhale()
}
xml file for fragment
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="ivakin.first.my_first_app.viewmodels.TimeSettingsViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/textView"
android:layout_width="95dp"
android:layout_height="59dp"
android:text="#{viewmodel.currentInhale}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.452" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Set"
android:onClick="#{()->viewmodel.onChange()"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
U Forgot Initialize ViewModel :
viewmodel = TimeSettingsViewModel()
Also dont use return binding.root

DataBinding doesn't update value when changing LiveData

I want the value to increase when I press the button and write it in the textview.
However, the code below shows the increasing value as Log
But, on screen, the value in textview does not change.
I'm not sure what's wrong. Please let me know if you know.
Fragment.kt
class SettingFragment : Fragment(), View.OnClickListener {
companion object {
fun newInstance() = SettingFragment()
private val TAG = "SettingFragment"
}
private lateinit var viewModel: SettingViewModel
private lateinit var binding: FragmentSettingBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_setting, container, false)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(SettingViewModel::class.java)
binding.lifecycleOwner = this
binding.button.setOnClickListener(this)
}
override fun onClick(v: View?) {
viewModel.increase()
Log.d(TAG, "Log Data" + viewModel.testInt.value)
}
}
FragmentViewModel
class SettingViewModel : ViewModel() {
val testInt: MutableLiveData<Int> = MutableLiveData()
init {
testInt.value = 0
}
fun increase() {
testInt.value = testInt.value?.plus(1)
}
}
fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.example.ui.setting.SettingViewModel" />
</data>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.setting.SettingFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{String.valueOf(viewModel.testInt)}" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
</layout>
<variable
name="viewModel"
type="com.example.ui.setting.SettingViewModel" />
This viewModel variable doesn't bind to a certain variable programmatically. So, when the value incremented, it won't reflect to the layout.
To fix this set the fragment's viewModel to this value:
viewModel = ViewModelProvider(this).get(SettingViewModel::class.java)
binding.viewModel = viewModel // <<<<< Here is the change
binding.lifecycleOwner = this
binding.button.setOnClickListener(this)
Side note:
You shouldn't use onActivityCreated() as it's is deprecated as of API level 28; you can normally its part of code to onCreateView(). And you can check here for other alternatives.

Android kotlin data-binding with fragments error:cannot find symbol import com.example.***.databinding.FragmentUserBindingImpl;

I am relatively new to kotlin and trying to build a project with data binding in some fragments.I have a fragment named UserFragment with a Recyclerview in it like this:
class UserFragment : Fragment() {
private lateinit var binding: FragmentUserBinding
private lateinit var viewModel: UserListViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding=DataBindingUtil.inflate(inflater,R.layout.fragment_user, container, false)
binding.userRecycler.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
viewModel = ViewModelProviders.of(this).get(UserListViewModel::class.java)
viewModel.errorMessage.observe(this, Observer {
errorMessage -> if(errorMessage != null) showError(errorMessage) else hideError()
})
binding.mViewModel=viewModel
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//start add activity
val i= Intent(activity,AddUserActivity::class.java)
userFab.setOnClickListener(View.OnClickListener {
startActivity(i)
})
}
private fun showError(#StringRes errorMessage:Int){
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
errorSnackbar?.dismiss()
}
}
and the xml layout file fragment_user.xml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="mViewModel"
type="com.example.***.ui.User.UserListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="#+id/userDateEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="#string/pick_date"
android:background="#drawable/roundededittext"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/userRecycler"
android:layout_width="293dp"
android:layout_height="475dp" android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#+id/userDateEditText"
app:adapter="#{viewModel.getUserListAdapter()}"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/userFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_marginBottom="48dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"/>
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:mutableVisibility="#{viewModel.getLoadingVisibility()}"
android:id="#+id/userProgressBar" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="140dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp" android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.804"
app:layout_constraintVertical_bias="0.499"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
there is also a similar adapter class and item_user.xml:
class UserListAdapter : RecyclerView.Adapter<UserListAdapter.ViewHolder>() {
private lateinit var userModelList:List<UserModel>
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListAdapter.ViewHolder {
val binding: ItemUserBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.item_user, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: UserListAdapter.ViewHolder, position: Int) {
holder.bind(userModelList[position])
}
override fun getItemCount(): Int {
return if(::userModelList.isInitialized) userModelList.size else 0
}
fun updateUserList(userModelList:List<UserModel>){
this.userModelList = userModelList
notifyDataSetChanged()
}
class ViewHolder(private val binding: ItemUserBinding):RecyclerView.ViewHolder(binding.root){
private val viewModel = UserViewModel()
fun bind(userModel: UserModel){
viewModel.bind(userModel)
binding.viewModel =viewModel
}
}
}
the item-user.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.example.***.ui.MyUser.UserViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
android:id="#+id/user_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textStyle="bold"
app:mutableText="#{viewModel.getUserTitle()}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/user_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:mutableText="#{viewModel.getUserDesc()}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/user_title" />
</android.support.constraint.ConstraintLayout>
</layout>
note that databinding has been enabled in the gradle
and the very important issue here is that in both my fragment and the adapter
this line:
binding.viewModel =viewModel
reports a type mismatch like this:
Type mismatch.
Required:MyUser.UserListViewModel?
Found:com.example.***.ui.MyUser.UserListViewModel
and when i build my project the error is as follows:
error: cannot find symbol
import com.example.***.databinding.FragmentUserBindingImpl;
The first error is pretty explicit: binding.viewModel expects a nullable UserListViewModel? and get a non-nullable UserListViewModel(see kotlin null safety doc).
You can try something like this to get rid of it:
Declare your viewModel as
private var viewModel: UserListViewModel? = null
and set your binding this way:
viewModel?.let{binding.viewModel = it}
Concerning the second error, your declarations seems fine, but sometimes the Android Studio's cache get corrupted, try Invalidate Caches/Restart, it may help.
Type mismatch.
Required:MyUser.UserListViewModel?
Found:com.example.***.ui.MyUser.UserListViewModel
Basically the error is telling that your
binding.viewModel //is a nullable type and there for it expects a nullable
//type to be assigned as well
So just turn your view model into a nullable type by addind the ? simbol after its delacarion (note late init types do not allow nullable types). try it like this
private var viewModel: UserListViewModel? = null
About the second error data binding library need to compile in order to autogenerate the binding class, just rebuild the project and this error will be gone.
Please feel free to use this template as a base to avoid all that boilerplate code
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.CLASS)
annotation class ContentView(#LayoutRes val id: Int)
fun ViewGroup.inflate(#LayoutRes layoutId: Int,
addContainer: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutId,this,addContainer)
}
#Suppress("UNCHECKED_CAST")
abstract class BaseFragment<Model : ViewModel, Binding : ViewDataBinding> : Fragment() {
/**
* Data binding class variable all view elements declared on the
* Xml file will be available within this instance if a view model
* Is required for the xml to work we will need to bind it on #onBindViewModel
*/
protected lateinit var binding: WeakReference<Binding?>
/**
* Fragments view model according to MVVM android architecture
* Each fragment class should have one , in order to facilitate
* Live Data and Binding library features, tho we can skip it
*/
protected lateinit var viewModel: WeakReference<Model?>
/**
* Here is where most likely you will get not null data , both binding and
* view model references can be destroyed by garbage collector
* If this application reaches low memory levels
*
* This optional method is used to bind the required view model inside the
* Xml file, this is optional to use tho is recommended
* Bind them by calling the view model binding.customViewModel = viewModel
*/
protected open fun onBindViewModel(viewModel: Model?, binding: Binding?) {}
/**
* There will be the occasion where custom params will be needed on view model's
* Constructor in this case will want to override the default creation #see ViewModelFactory
*/
protected open fun onCreateViewModel(modelType: Class<Model>): Model? = if (modelType != ViewModel::class.java)
ViewModelProviders.of(requireActivity()).get(modelType) else null
/**
* Here we will inherit view model and binding values based on the class
* Parameters and store them in global variables so any class extending
* From Base activity has access to binding and view model instances by default
*/
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val layout = this.javaClass.annotations.find { it.annotationClass == ContentView::class } as ContentView?
?: throw RuntimeException("You annotate this class with #ContentView and provide a layout resource")
container?.let { binding = WeakReference(DataBindingUtil.bind(it.inflate(layout.id))!!) }
?: run { binding = WeakReference(DataBindingUtil.bind(inflater.inflate(layout.id, null))) }
viewModel = WeakReference(
onCreateViewModel(
(this.javaClass.genericSuperclass
as ParameterizedType).actualTypeArguments[0] as Class<Model>
)
)
setHasOptionsMenu(true)
onBindViewModel(viewModel.get(), binding.get())
return binding.get()?.root
}
}
And just use it like this (see how much boiler plate code is gone)
#ContentView(R.layout.fragment_user)
class UserFragment: BaseFragment<UserListViewModel, FragmentUserBinding> {
override fun onBindViewModel(viewModel: UserListViewModel?, binding: FragmentUserBinding?) {
binding.viewModel = viewModel
}
}

Categories

Resources