Binding Adapter live data value is always null - android

For some reason, the second parameter value for both binding Adapters always returns null and I cannot figure out why. I am selecting a plantIndividual from a RecyclerView in the overview fragment and using it to navigate to a details page - individual fragment. Both Fragments share a viewModel.
Here are my BindingAdapters:
#BindingAdapter("listPhotoData")
fun bindPlantRecyclerView(recyclerView: RecyclerView,
data: List<PlantPhoto>?) {
val adapter = recyclerView.adapter as CollectionIndividualAdapter
adapter.submitList(data)
}
#BindingAdapter("singleImage")
fun loadImage(imgView: ImageView, imgUrl: File) {
imgUrl.let {
Glide.with(imgView.context)
.load(imgUrl)
.apply(
RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
My details fragment layout:
<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.example.collection.presentation.overview.CollectionOverviewViewModel" />
<variable
name="plantPhoto"
type="com.example.storage.data.PlantPhoto" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<ImageView
android:id="#+id/collection_individual_imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleImage="#={viewModel.plantPhotoDisplay.plantFilePath}"
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.26999998"
tools:srcCompat="#tools:sample/avatars" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/collection_individual_recyclerview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/collection_individual_imageview"
app:layout_constraintVertical_bias="0.498"
app:listPhotoData="#={viewModel.listPlantPhoto}"
tools:listitem="#layout/image_plant_photo_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
ViewModel:
class CollectionOverviewViewModel(application: Application) : AndroidViewModel(application) {
lateinit var mediaPlantList: MutableList<File>
private var newPhotoList = mutableListOf<PlantPhoto>()
private val context = getApplication<Application>().applicationContext
private val _navigateToSelectedPlant = MutableLiveData<PlantIndividual>()
val navigateToSelectedPlant: LiveData<PlantIndividual>
get() = _navigateToSelectedPlant
private val _listPlantPhoto = MutableLiveData<MutableList<PlantPhoto>>()
val listPlantPhoto: LiveData<MutableList<PlantPhoto>>
get() = _listPlantPhoto
private val _plantPhotoDisplay = MutableLiveData<PlantPhoto>()
val plantPhotoDisplay: LiveData<PlantPhoto>
get() = _plantPhotoDisplay
fun displayPlantDetails(plantIndividual: PlantIndividual) {
_navigateToSelectedPlant.value = plantIndividual
}
fun displayPlantDetailsComplete() {
_navigateToSelectedPlant.value = null
}
fun retrievePlantList(plantIndividual: PlantIndividual) {
val dataClassNum = plantIndividual.plantId
viewModelScope.launch {
mediaPlantList = context?.getExternalFilesDir("planio/dataclasses/$dataClassNum")
?.listFiles()?.sortedDescending()?.toMutableList() ?: mutableListOf()
}
}
fun changeToPlantPhotos(plantList: MutableList<File>) {
plantList.map {
val file = FileInputStream(it)
val inStream = ObjectInputStream(file)
val item = inStream.readObject() as PlantPhoto
newPhotoList.add(item)
}
_plantPhotoDisplay.value = newPhotoList.last()
_listPlantPhoto.value = newPhotoList
}
}
OverView Fragment from which I am selecting a plantIndividual from a RecyclerView and navigating to a details page:
viewModel.navigateToSelectedPlant.observe(viewLifecycleOwner, {
if (null != it) {
viewModel.retrievePlantList(it)
viewModel.changeToPlantPhotos(viewModel.mediaPlantList)
this.findNavController().navigate(
CollectionOverviewFragmentDirections.
actionCollectionOverviewFragmentToCollectionIndividualFragment(it))
}
})
Details Fragment:
class CollectionIndividualFragment: Fragment() {
private lateinit var binding: FragmentCollectionIndividualBinding
private val viewModel: CollectionOverviewViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_collection_individual, container,
false)
binding.toCollectionOverview.setOnClickListener {
this.findNavController().navigate(CollectionIndividualFragmentDirections.
actionCollectionIndividualFragmentToCollectionOverviewFragment())
viewModel.displayPlantDetailsComplete()
}
binding.viewModel = viewModel
binding.lifecycleOwner = this
binding.collectionIndividualRecyclerview.adapter = CollectionIndividualAdapter()
binding.plantPhoto = viewModel.plantPhotoDisplay.value
return binding.root
}

I guess you're setting the lifecycleOwner after setting the viewModel of your binding and as a result, after viewModel is set in binding, it cannot observe live data because the lifecycleOwner is null at that point. I suggest to set it after setting binding
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_collection_individual, container,
false)
binding.lifecycleOwner = this
Edit:
Also don't forget to use by activityViewModels instead of by viewModels to share viewModel among your fragments

Related

Databinding with Livedata not showing values

Update:
I found out that there is a problem with the async task, while setting the LiveData
I'm using MVVM in my App and I try to bind Livedata to a TextView with Jetpack Databinding.
I get the correct values in the Log.d() of my Viewmodel, however the data is not shown on Screen.
Could someone guess what am I missing?
My XML File
(fragment_lobbyleader.xml)
<data >
<variable
name="viewmodel"
type="com.example.beerrallye.lobby.newLobbyData.LobbyViewModel" />
</data>
<TextView
android:id="#+id/lobbyleader_lobbyCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="#{viewmodel.lobbyID}"
android:textColor="#color/black"
app:layout_constraintBottom_toTopOf="#id/lobbyleader_player_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
My Viewmodel
class LobbyViewModel(private val lobbyRepository: LobbyRepository) : ViewModel() {
private val _lobbyID = MutableLiveData<String?>()
val lobbyID : LiveData<String?> = _lobbyID
fun createLobby(lobby: Lobby) {
viewModelScope.launch {
val result = lobbyRepository.createLobby(lobby)
Log.d("Lobby", "Viewmodel Data: ${result}")
if (result is Result.Success) {
_lobbyID.value = result.data.lobbyID
Log.d("Lobby", "Viewmodel Data: ${_lobbyID.value}")
}
}
}
My Fragment
class LobbyLeaderFragment : Fragment() {
private val lobbyViewmodel: LobbyViewModel by viewModels {
LobbyViewModelFactory((activity?.application as AppApplication).lobbyRepository)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentLobbyleaderBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_lobbyleader,
container,
false
)
binding.viewmodel = lobbyViewmodel
binding.lifecycleOwner = viewLifecycleOwner
lobbyViewmodel.lobbyID.observe(viewLifecycleOwner, {
Log.d("Lobby", "LobbyCode: $it")
})
return binding.root
}
}
Can try switching the order of
binding.viewmodel = lobbyViewmodel
binding.lifecycleOwner = viewLifecycleOwner
And turn it into
binding.lifecycleOwner = viewLifecycleOwner
binding.viewmodel = lobbyViewmodel
It's probable that when it wants to observe livedata, the lifecycleOwner is null and thus it cannot observe it
Also check this Binding Adapter live data value is always null

Android recyclerView and data binding

I make an Android application in Kotlin, and i want to get some information from the webserver and put in a recycler view using data binding. I use repository pattern, so each time i make a get request, i save the information in room database and get data from it with live data. For some reason my ryclerview show nothing, and i get no errors. The request work and i get the data but the recyclerview not working.
My ViewModel:
class HomeViewModel(application: Application) : ViewModel() {
private val availableTicketsRepo = AvailableTicketsRepository(getInstance(application))
val tickets = availableTicketsRepo.availableTickets
init {
refreshDataFromRepository()
}
private fun refreshDataFromRepository() {
viewModelScope.launch {
try {
availableTicketsRepo.refreshAvailableTickets()
} catch (ex: Exception) {
ex.message?.let { Resource.error(data = null, message = it) }
Log.i("Home", "*** Eccezione: ${ex} ***")
}
}
}
My fragment class (with adapter and viewholder):
package com.example.ticketapp.ui.home
class HomeFragment : Fragment() {
private val viewModel: HomeViewModel by lazy {
val activity = requireNotNull(this.activity) {
"You can only access the viewModel after onActivityCreated()"
}
ViewModelProvider(this, HomeViewModel.Factory(activity.application))
.get(HomeViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = DataBindingUtil.inflate<FragmentHomeBinding>(
inflater,
R.layout.fragment_home,
container,
false
)
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.availableTicketsList.adapter = AvailableTicketsAdapter()
return binding.root
}
}
class AvailableTicketsAdapter() :
ListAdapter<AvailableTicket, AvailableTicketsAdapter.AvailableTicketViewHolder>(DiffCallback) {
class AvailableTicketViewHolder(private var binding: AvailableTicketItemsBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(tickets: AvailableTicket) {
binding.ticket = tickets
binding.executePendingBindings()
}
}
companion object DiffCallback : DiffUtil.ItemCallback<AvailableTicket>() {
override fun areItemsTheSame(oldItem: AvailableTicket, newItem: AvailableTicket): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(
oldItem: AvailableTicket,
newItem: AvailableTicket
): Boolean {
return oldItem.Id == newItem.Id
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AvailableTicketViewHolder {
return AvailableTicketViewHolder(
AvailableTicketItemsBinding.inflate(
LayoutInflater.from(
parent.context
)
)
)
}
override fun onBindViewHolder(holder: AvailableTicketViewHolder, position: Int) {
val tickets = getItem(position)
holder.bind(tickets)
}
}
My repository:
class AvailableTicketsRepository(private val database: TicketDatabase) {
val availableTickets: LiveData<List<AvailableTicket>> =
Transformations.map(database.ticketDatabaseDao.getAvailableTickets()) {
it.asDomainModel()
}
suspend fun refreshAvailableTickets() {
withContext(Dispatchers.IO) {
val tickets = RetrofitBuilder.apiService.getAvailableTickets()
database.ticketDatabaseDao.insertAllAvailableTickets(tickets.map { it.asDataBaseModel() })
}
}
}
The xml for items in recylerview:
<?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>
<import type="android.view.View"/>
<variable
name="ticket"
type="com.example.ticketapp.model.AvailableTicket" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/titleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLength="50"
android:text="#{ticket.title}"
android:singleLine="true"
android:textAlignment="center"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Titolo di max 50 caratteri per non uscire dalla riga" />
<TextView
android:id="#+id/customerText"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="14sp"
android:text="#{ticket.customer}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/titleText"
tools:text="ESEMPIO DI UN REFERENTE" />
<TextView
android:id="#+id/dateText"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textAlignment="textEnd"
android:textSize="14sp"
android:text="#{ticket.date_Time}"
app:layout_constraintBaseline_toBaselineOf="#+id/customerText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/titleText"
tools:text="20/01/2021 10:00:00" />
<ImageView
android:id="#+id/warningImg"
style="#style/iconImg"
android:layout_marginTop="20dp"
android:visibility="#{ticket.triangle ? View.VISIBLE : View.GONE}"
android:foregroundGravity="center"
app:layout_constraintEnd_toStartOf="#+id/dateText"
app:layout_constraintStart_toEndOf="#+id/customerText"
app:layout_constraintTop_toBottomOf="#+id/titleText"
app:srcCompat="#drawable/ic_warning" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
XML of ryclerview:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/availableTicketsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingEnd="10dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar"
tools:listitem="#layout/available_ticket_items" />
My Model class:
#Parcelize
data class AvailableTicket (
val Id: Int,
val Title: String,
val Description: String,
val Date_Time: String,
val Customer: String,
val Field: String,
val Category: String,
val Color: String,
val Author: String,
val Triangle: Boolean,
val MyPriority: String
): Parcelable
You should, notify adapter when data is modified, add a LiveData inside ViewModel and post it after availableTicketsRepo.refreshAvailableTickets(), then call submitList inside adapter once the data is received
ViewModel
class HomeViewModel(application: Application) : ViewModel() {
private val availableTicketsRepo = AvailableTicketsRepository(getInstance(application))
val tickets = availableTicketsRepo.availableTickets
init {
refreshDataFromRepository()
}
private fun refreshDataFromRepository() {
viewModelScope.launch {
try {
availableTicketsRepo.refreshAvailableTickets()
} catch (ex: Exception) {
ex.message?.let { Resource.error(data = null, message = it) }
Log.i("Home", "*** Eccezione: ${ex} ***")
}
}
}
}
Then in Fragment, submit list to adapter once fetched
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = DataBindingUtil.inflate<FragmentHomeBinding>(
inflater,
R.layout.fragment_home,
container,
false
)
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.availableTicketsList.adapter = AvailableTicketsAdapter()
viewModel.tickets.observe(this, Observer{
(binding.availableTicketsList.adapter as? AvailableTicketsAdapter)?.submitList(it)
})
return binding.root
}

Android: LiveData postValue() getting null

I am trying to transfer a value from one LiveData (Repository.getMovieList(editTextContent.value.toString()).value) to another LiveData (this.movieList.postValue) using postValue().
I am observing the movieList and want to change it's value from the Repo depending on different buttons that were clicked but I when it runs, it only gets the null value and doesn't wait till the Repo's LiveData gets their value.
Fragment xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="com.example.movieapp.ui.search.SearchMovieFragmentViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.search.SearchMovieFragment">
<EditText
android:id="#+id/search_movie_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={viewmodel.editTextContent}"
android:inputType="text"
android:hint="Movie Name" />
<Button
android:id="#+id/search_fragment_search_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Search"
android:onClick="#{() -> viewmodel.getMovieSearchList()}"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/search_movie_fragment_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</layout>
SearchMovieFragment
class SearchMovieFragment : Fragment(), MovieSearchItemViewModel {
companion object {
fun newInstance() = SearchMovieFragment()
}
private lateinit var searchMovieFragmentViewModel: SearchMovieFragmentViewModel
private lateinit var binding: SearchMovieFragmentBinding
private lateinit var movieRecyclerView: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.search_movie_fragment, container, false)
searchMovieFragmentViewModel = ViewModelProvider(this).get(SearchMovieFragmentViewModel::class.java)
binding.lifecycleOwner = this
binding.viewmodel = searchMovieFragmentViewModel
setUpRecyclerView(container!!.context)
return binding.root
}
private fun setUpRecyclerView(context: Context) {
movieRecyclerView = binding.searchMovieFragmentRecyclerView.apply {
this.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}
val adapter = MovieListAdapter()
adapter.setCallback(this)
binding.searchMovieFragmentRecyclerView.adapter = adapter
searchMovieFragmentViewModel.getMovieListLiveData().observe(viewLifecycleOwner, Observer {movieList ->
adapter.submitList(movieList)
})
}
}
SearchMovieViewModel
class SearchMovieFragmentViewModel : ViewModel() {
val editTextContent = MutableLiveData<String>()
var movieList: MutableLiveData<List<Movie>> = MutableLiveData()
fun getMovieSearchList(){
this.movieList.postValue(Repository.getMovieList(editTextContent.value.toString()).value)
}
fun getTrendingMovies() {
movieList.postValue(Repository.getTrendingMovies().value)
}
fun getMovieDetail(movieId: String): MutableLiveData<Movie> {
return Repository.getMovieDetail(movieId)
}
fun getMovieListLiveData() : LiveData<List<Movie>> {
return movieList
}
private fun getMovieList(movieSearch: String): MutableLiveData<List<Movie>> = Repository.getMovieList(movieSearch)
}
I think you are implementing it the wrong way, Instead, using the MediatorLiveData will be a good and practical solution as it allows you to observe multiple LiveData objects and select between them based on your preferences (a specific action for example).
This is an example of how to implement it in your case
val editTextContent = MutableLiveData<String>()
val finalList = MediatorLiveData<List<Movie>>()
// Here.. Define all of your LiveData objects
private val movieList = repository.getMovieList(editTextContent.value.toString())
private val trendingMovies = repository.getTrendingMovies()
private val movieDetail = repository.getMovieDetail()
fun setSelection(selection: String) {
finalList.addSource(movieList) { result ->
if (selection == "movieList") {
result?.let { finalList.value = it }
}
}
finalList.addSource(trendingMovies) { result ->
if (selection == "trendingMovies") {
result?.let { finalList.value = it }
}
}
finalList.addSource(movieDetail) { result ->
if (selection == "movieDetail") {
result?.let { finalList.value = it }
}
}
}
So what you have to do is to only observe the MediatorLiveData and then call the setSelection function and send the correspondent selection action to it as a parameter and it will switch the observation to another LiveData

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