Android View Model binding in kotlin - android

i am facing a strange issue. I am binding a viewModel to my layout by factory design patter. When ever i build that there is difference between inferred and expected view model.
My View Model Class
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
var alarmList = database.getAllAlarms()
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
fun onStartTracking() {
uiScope.launch {
val newNight = AlarmModel()
insert(newNight)
}
}
private suspend fun insert(night: AlarmModel) {
withContext(Dispatchers.IO) {
database.insert(night)
}
}
}
So when every i try to assign the view model in fragment, it shows error that expected and inferred view model are different
My XML file
<?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.android.trackmysleepquality.Fragments.SleepTrackerViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- TODO: Update blank fragment layout -->
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:listData="#{viewModel.alarmList}" />
</LinearLayout>
</layout>
My Factory Class
class SleepTrackerViewModelFactory(
private val dataSource: SleepDatabaseDao,
private val application: Application) : ViewModelProvider.Factory {
#Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
return SleepTrackerViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

Remove ViewModel from your XML file and try this one.
class AddMovieFragment : Fragment() {
private lateinit var viewModel: AddMovieViewModel
companion object {
const val SEARCH_QUERY = "SEARCH_QUERY"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(AddMovieViewModel::class.java)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_add_movie, container, false)
}

Related

Flow is not working with Binding Adapters

For a particular Recycler View Item, If I select the Checkbox (tick it) then I need the text of its corresponding TextView to formatted as Strikethrough.
I am using Binding Adapters, Flow and Live Data.
But after selecting the checkbox, its corresponding TextView is not getting formatted.
But If I navigate to some other fragment and come back to here(FruitFragmnet) then the TextView data is formatted. (i.e. the database gets updated correctly on ticking checkbox but the live data emission is delayed to UI)
Possible Root Cause: My update to Room Database is happening immeialtey, but from database the LiveData is not flown to UI immediately.
I did lot of trial and errors, read multiple similar questions but I was unable to find the missing link and solution to this issue.
Please advice. Following is the code:
BindingAdapter
#BindingAdapter("markAsCompleted")
fun markAsCompleted(textView: TextView, completed: Boolean) {
if (completed) {
textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
} else {
textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
#BindingAdapter("setItems")
fun setItems(view: RecyclerView, items: List<Fruit>?) {
items?.let {
(view.adapter as SettingAdapter).submitList(items)
}
}
Fruit Fragment with Recycler View
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="settingViewModel"
type="com.example.ui.SettingViewModel" />
</data>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/fruits_list"
setItems="#{settingViewModel.allList}" // This is Binding Adapter
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</layout>
Above Fruit's Fragment Item View
<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>
<import type="android.widget.CompoundButton" />
<variable
name="fruit"
type="com.example.data.Fruit" />
<variable
name="settingViewModel"
type="com.example.ui.SettingViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
...
<CheckBox
android:id="#+id/fruit_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="#{fruit.completed}"
android:onClick="#{(view) -> settingViewModel.completeFruit(fruit,((CompoundButton)view).isChecked())}"
/>
<TextView
android:id="#+id/fruit_name"
markAsCompleted="#{fruit.completed}" // This is Binding Adapter
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#{fruit.fruit}" />
....
Fruit Fragment
class FruitFragment : Fragment() {
private lateinit var binding: FragmentFruitBinding
private lateinit var fruitAdapter: FruitAdapter
private val viewModel: SettingViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentFruitBinding.inflate(layoutInflater, container, false).apply {
lifecycleOwner = viewLifecycleOwner
settingViewModel = viewModel
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fruitAdapter = FruitAdapter(viewModel)
binding.fruitslist.apply {
adapter = fruitAdapter
}
}
}
SettingViewModel
class SettingViewModel(application: Application) : AndroidViewModel(application) {
private val app = getApplication<Application>()
private val dao = Database.getDatabase(app.applicationContext).dao
val allList: LiveData<List<Fruit>> = dao.getFruits().asLiveData().distinctUntilChanged()
fun completeFruit(fruit: Fruit, completed: Boolean) {
viewModelScope.launch {
if (completed) {
dao.updateCompleted(fruit.id, completed)
} else {
dao.updateCompleted(fruit.id, completed)
}
}
}
....
}
DAO Class
#Dao
interface DatabaseDao {
#Query("SELECT * FROM fruit_table")
fun getFruits(): Flow<List<Fruit>>
#Query("UPDATE fruit_table SET completed = :completed WHERE id = :id")
suspend fun updateCompleted(id: Int, completed: Boolean)
}
Recycler View Adapter
class FruitAdapter(private val viewModel: SettingViewModel) : ListAdapter<Fruit, ViewHolder>(FruitDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item, viewModel)
}
class ViewHolder private constructor(val binding: ContainerFruitBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Fruit, viewModel: SettingViewModel) {
binding.apply {
settingViewModel = viewModel
fruit = item
executePendingBindings()
}
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ContainerFruitBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class FruitDiffCallback : DiffUtil.ItemCallback<Fruit>() {
override fun areItemsTheSame(oldItem: Fruit, newItem: Fruit): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Fruit, newItem: Fruit): Boolean {
return oldItem.fruit == newItem.fruit
}
}
Data Class
#Entity(tableName = "fruit_table")
data class Fruit(
#PrimaryKey(autoGenerate = true)
var id: Int = 0,
var fruit: String,
var completed: Boolean = false
)
I guess you need to change the second parameter of setItems function to LiveData in BindingAdapter:
#BindingAdapter("setItems")
fun setItems(view: RecyclerView, data: LiveData<List<Fruit>>) {
data.value?.let {
(view.adapter as SettingAdapter).submitList(it)
}
}

Changing toolbar title in each fragment using MVVM and Databinding

I am looking to change the toolbar title,which is in my main activity, in my fragments page. My project is based on MVVM Architecture, with databinding.
This is my main_activity.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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.lalsoft.toolbar_mvvm_databinding.viewmodel.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:minHeight="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:titleTextColor="#android:color/white"
app:navigationIcon="#drawable/ic_arrow_back"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay"
app:navigationOnClickListener="#{()->viewModel.navBackClicked()}"
app:title="#{viewModel.toolbarTitle}"/>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
This is my mainActivity.kt
private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
private lateinit var dataBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
//setSupportActionBar(dataBinding.toolbar)
//dataBinding.toolbar.setNavigationIcon(R.drawable.ic_arrow_back)
viewModel.navClicked.observe(this, navClickObserver)
viewModel.toolbarTitle.observe(this, toolbarTitleObserver)
dataBinding.viewModel = viewModel
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction().replace(
R.id.fragment_container,
FirstFragment()
).commit()
}
}
private val navClickObserver = Observer<Boolean> {
supportFragmentManager.popBackStack()
Log.e(TAG, "Nav Back clicked")
}
private val toolbarTitleObserver = Observer<String> {
Log.e(TAG, "Title set : $it")
}
}
And this is my MainViewModel
private const val TAG = "MainViewModel"
open class MainViewModel : ViewModel() {
val toolbarTitle: MutableLiveData<String> = MutableLiveData()
private val _navClicked: MutableLiveData<Boolean> = MutableLiveData()
val navClicked: LiveData<Boolean> = _navClicked
init {
Log.e(TAG, "Inside Init")
//toolbarTitle.value ="Main Activity"
}
fun navBackClicked() {
_navClicked.value = true
}
}
Now i am trying to change the toolbar title in FragmentViewModel by changing the mutable toolbarTitle of my mainActivityViewModel.
private const val TAG = "FirstViewModel"
class FirstViewModel : MainViewModel() {
private val _navigateToDetails = MutableLiveData<Event<String>>()
val navigateToFragment: LiveData<Event<String>>
get() = _navigateToDetails
init {
Log.e(TAG, "Inside Init")
toolbarTitle.value="First Fragment"
}
fun onBtnClick() {
_navigateToDetails.value = Event("Second Fragment")
}
}
This is my fragment class
private const val TAG = "FirstFragment"
class FirstFragment : Fragment() {
private lateinit var viewModel: FirstViewModel
private lateinit var dataBinding: FirstFragmentBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
dataBinding = DataBindingUtil.inflate(inflater, R.layout.first_fragment, container, false)
return dataBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(FirstViewModel::class.java)
viewModel.toolbarTitle.observe(viewLifecycleOwner, toolbarTitleObserver)
viewModel.navigateToFragment.observe(viewLifecycleOwner, navigateToFragmentObserver)
//(activity as MainActivity?)!!.toolbar.title = "Check"
dataBinding.viewModel = viewModel
}
private val toolbarTitleObserver = Observer<String> {
Log.e(TAG, "Title set : $it")
//(activity as MainActivity?)!!.toolbar.title = "Check"
//Log.e(TAG, "Title set : Check")
}
private val navigateToFragmentObserver = Observer<Event<String>> { it ->
it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
Log.i(TAG, "checkIt string $it")
parentFragmentManager.beginTransaction().replace(
R.id.fragment_container,
SecondFragment()
).addToBackStack(null).commit()
}
}
}
Eventhough its observing the toolbarTitle correctly,the Title in my program is not changing..
Hope to get some help to get out of this issue.
This is my sample git project where i am trying to do this : github
I struggle with the same!
I think the problem is the lifecycleowner. But could not find an answer.
Currently I observe the values from the the ViewModel and assign the values inside the observer.
But I think there is a better way!
If your Fragment is using a ViewModel should be scoped to a host Activity, use by activityViewModels() delegate:
#AndroidEntryPoint
class HomeFragment : Fragment() {
private val viewModel: SharedViewModel by activityViewModels()
}
I think also this answer will help.
https://stackoverflow.com/a/62560605
When you use by viewModels, you are creating a ViewModel scoped to that individual Fragment - this means each Fragment will have its own individual instance of that ViewModel class. If you want a single ViewModel instance scoped to the entire Activity, you'd want to use by activityViewModels

How to open a fragment from another fragment using MVVM

I have a fragment ProductsFragment in which I have a button AddProduct when it is clicked I want to open a different fragment AddProductFragment.
I am using MVVM architecture
I went through this link and done the below mentioned implementation, but I did not quite understand or did not mention where fragment I want to navigate to
Error message
ProductsFragment - THE ISSUE IS HERE IN ONVIEWCREATED METHOD*
class ProductsFragment: Fragment() {
private lateinit var binding: ProductsBinding
private lateinit var navController: NavController
private lateinit var productsViewModel: ProductsViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.products, container, false)
val dao = SubscriberDatabase.getInstance(activity!!.applicationContext).productDAO
val repository = ProductRepository(dao)
val factory = ProductsViewModelFactory(repository, activity!!.applicationContext)
productsViewModel = ViewModelProvider(this, factory).get(ProductsViewModel::class.java)
binding.productsViewModel = productsViewModel
binding.lifecycleOwner = this
val view = binding.root
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
productsViewModel.navigateScreen.observe(activity!!, EventObserver {
navController.navigate(it) //issues is here
})
}
}
Products
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".ProductsBinding">
<variable
name="productsViewModel"
type="com.rao.iremind.ProductsViewModel" />
</data>
<LinearLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Testing text"/>
<Button
android:id="#+id/btn_add_product"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add product"
android:onClick="#{() -> productsViewModel.addProduct()}"/>
<View
android:id="#+id/frgSpace"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</layout>
ProductViewModel
class ProductsViewModel (
private val repository: ProductRepository,
private val context: Context
): ViewModel() {
private val _navigateScreen = MutableLiveData<Event<Any>>()
val navigateScreen: LiveData<Event<Any>> = _navigateScreen
fun addProduct() {
Toast.makeText(context, "Products view model", Toast.LENGTH_LONG).show()
_navigateScreen.value = Event(R.id.frgSpace)
}
}
Event
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
class EventObserver<Int>(private val onEventUnhandledContent: (Int) -> Unit) : Observer<Event<Int>> {
override fun onChanged(event: Event<Int>?) {
event?.getContentIfNotHandled()?.let {
onEventUnhandledContent(it)
}
}
}
ProductsViewModelFactory
class ProductsViewModelFactory (
private val repository: ProductRepository,
private val context: Context
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ProductsViewModel::class.java)) {
return ProductsViewModel(repository, context) as T
}
throw IllegalArgumentException("Unknown View Model class")
}
}
I want to navigate to this fragment
class AddProductFragment: Fragment() {
private lateinit var binding: AddProductBinding
private lateinit var addProductViewModel: AddProductViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.add_product, container, false)
val dao = SubscriberDatabase.getInstance(activity!!.applicationContext).productDAO
val repository = ProductRepository(dao)
val factory = ProductsViewModelFactory(repository, activity!!.applicationContext)
addProductViewModel = ViewModelProvider(this, factory).get(AddProductViewModel::class.java)
binding.addProductViewModel = addProductViewModel
binding.lifecycleOwner = this
val view = binding.root
return view
}
}
Thanks
R
It seems that your EventObserver class is expecting an Int but you are sending Any in LiveData<Event<Any>>
Try changing
private val _navigateScreen = MutableLiveData<Event<Any>>()
val navigateScreen: LiveData<Event<Any>> = _navigateScreen
to
private val _navigateScreen = MutableLiveData<Event<Int>>()
val navigateScreen: LiveData<Event<Int>> = _navigateScreen
I would also recommend you to replace activity!! with viewLifecycleOwner in this line:
productsViewModel.navigateScreen.observe(viewLifecycleOwner, EventObserver {...})
so that your fragment does not receive any LiveData updates when its view is destroyed.

DialogFragment with view model not working with data binding

I've created one dialog fragemnt with view model (mvvm). Dialog consist of one button (custom view). when using view model with data binding, button click is not working when livedata change.I'm using boolean value to check if button is clicked or not. What is causing issue? Also suggest any other approach if needed.
profile_dialog_fragment.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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="com.test.ui.ProfileDialogViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.ProfileDialog">
<com.google.android.material.button.MaterialButton
android:id="#+id/login"
style="#style/TextAppearance.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login"
android:onClick="#{() -> viewmodel.onLoginButtonClick()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
ProfileDialog.kt
class ProfileDialog : DialogFragment() {
companion object {
fun newInstance() = ProfileDialog()
}
private val viewModel: ProfileDialogViewModel by viewModel()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = ProfileDialogFragmentBinding.inflate(inflater, container, false)
.apply {
this.lifecycleOwner = this#ProfileDialog
this.viewmodel = viewmodel
}
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel.startLogin.observe(viewLifecycleOwner, Observer {
Log.d("insta", "This is working")
if (it == null) return#Observer
if(it) {
Log.d("insta", "This is not working")
val loginIntent = Intent(this.context, LoginActivity::class.java)
this.context?.startActivity(loginIntent)
}
})
}
}
ProfileDialogViewModel.kt
class ProfileDialogViewModel : ViewModel() {
private val _startLogin = MutableLiveData<Boolean>(false)
val startLogin: LiveData<Boolean>
get() = _startLogin
fun onLoginButtonClick() {
Log.d("insta", "This ain't working")
_startLogin.postValue(true)
}
}
Your viewmodel is defined in
private val viewModel: ProfileDialogViewModel by viewModel()
So, pay attention to viewModel. The problem located in
this.viewmodel = viewmodel
where this points to ProfileDialogFragmentBinding. Here you assinging ProfileDialogFragmentBinding.viewmodel = ProfileDialogFragmentBinding.viewmodel - that's why it's not working.
To solve problem, properly assign it like that:
this.viewmodel = viewModel

Data binding returning null

I might be doing this all wrong, but I have the same exact implementation in another fragment/viewmodel with no problems. Maybe because it's a dialog? Every time I log message or message.messagebody it returns null. Can anyone maybe point out why? Currently learning mvvm.
xml: (the important bit since it's long)
<?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="user"
type="com.catbellystudio.knodee.models.Users" />
<variable
name="vm"
type="com.catbellystudio.knodee.ui.profile.ProfileViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_margin="10dp"
android:background="#drawable/custom_background_popup"
android:elevation="10dp"
android:orientation="vertical">
<ScrollView
android:id="#+id/popupTextLayout"
android:layout_width="match_parent"
android:layout_height="277dp"
android:layout_marginTop="8dp">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"
android:hint="#string/your_message"
android:inputType="textMultiLine"
android:padding="10dp"
android:text="#{vm.message.messageBody}" />
</ScrollView>
</LinearLayout>
</layout>
viewmodel:
class ProfileViewModel(
private val userRepository: UserRepository,
private val messageRepository: MessageRepository
) : ViewModel() {
var message: Message = Message()
var sender: Users? = null
var receiver: Users? = null
var string:String?=null
fun getLoggedInUser() = runBlocking { userRepository.getUser() }
fun onBackPressed(view: View) {
Navigation.findNavController(view).navigateUp()
}
fun postMessage(view:View) {
Coroutines.main {
Log.e("messagevm", message.toString())
}
}
}
fragment:
class MessageFragment : Fragment(), KodeinAware {
private lateinit var viewModel: ProfileViewModel
private lateinit var profileBinding: FragmentProfileBinding
private lateinit var popupBinding: FragmentPopupBinding
override val kodein by kodein()
private val factory: ProfileViewModelFactory by instance()
private lateinit var dialog: Dialog
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProviders.of(this, factory).get(ProfileViewModel::class.java)
profileBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_profile,
container,
false
)
popupBinding = FragmentPopupBinding.inflate(LayoutInflater.from(context))
dialog = context?.let { Dialog(it) }!!
dialog.setContentView(popupBinding.root)
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
profileBinding.viewModel = viewModel
popupBinding.vm = viewModel
getSender()
return profileBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
settingsButtonProfile.visibility = View.GONE
messageButtonProfile.setOnClickListener {
showPopUp()
}
val receiver by lazy {
arguments?.let { fromBundle(it).user }
}
viewModel.receiver = receiver
}
private fun showPopUp() {
dialog.show()
val switch = dialog.visibilitySwitchPopup
val visibilityTextView = dialog.visibilityTextViewPopup
dialog.closeButtonPopUp?.setOnClickListener {
dialog.dismiss()
}
switch?.setOnClickListener {
val isIconEnabled = switch.isIconEnabled
if (isIconEnabled) {
visibilityTextView?.text = getString(R.string.anonymous_prompt)
} else {
visibilityTextView?.text = getString(R.string.anonymous_warning)
}
switch.switchState()
}
}
private fun getSender() {
viewModel.getLoggedInUser()?.let { viewModel.sender = it }
}
}
Any help would be appreciated!
move this "popupBinding.vm = viewModel" line to onViewCreated() method and also include this line "popupBinding.lifeCycleOwner=this" in same method
Solved by changing
android:text="#{vm.message.messageBody}
to
android:text="#={vm.message.messageBody}

Categories

Resources