I have this code in ViewModel class to display a progressBar when data is loading :
class DetailViewModel(
context: Application,
private val schedulerProvider: BaseSchedulerProvider,
private val dataSource: RemoteDataSource
) : AndroidViewModel(context) {
val isFeedsLoading = ObservableBoolean(false)
fun showFeeds(goal: SavingsGoal): Disposable? {
EspressoIdlingResource.increment() // App is busy until further notice
isFeedsLoading.set(true)
return dataSource.getFeeds(goal.id)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.doFinally {
if (!EspressoIdlingResource.getIdlingResource().isIdleNow) {
EspressoIdlingResource.decrement()
}
isFeedsLoading.set(false)
}
?.subscribe({ feeds ->
}
) {
Timber.e(it)
}
}
And the layout :
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
app:items="#{vm.feeds}"/>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:visibleGone="#{vm.isFeedsLoading}"/>
</FrameLayout>
Problem : When we RESUME the Fragment. It shows ProgressBar and the visibility is not Gone as expected. What could be the reason?
And my Fragment :
#ActivityScoped
class DetailFragment #Inject
constructor() // Required empty public constructor
: DaggerFragment() {
#Inject
lateinit var viewModelFactory: DetailViewModel.DetailViewModelFactory
#Inject
lateinit var goal: SavingsGoal
private val compositeDisposable = CompositeDisposable()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val viewModel = ViewModelProviders.of(this, viewModelFactory)[DetailViewModel::class.java]
val root = inflater.inflate(R.layout.fragment_detail, container, false)
val binding = FragmentDetailBinding.bind(root).apply {
setVariable(BR.vm, viewModel)
goal = this#DetailFragment.goal
lifecycleOwner = this#DetailFragment
}
with(root) {
with(activity as AppCompatActivity) {
setupActionBar(binding.toolbar) {
setDisplayShowTitleEnabled(false)
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
}
}
binding.vm?.showFeeds(goal)?.let { compositeDisposable.add(it) }
return root
}
override fun onDestroyView() {
super.onDestroyView()
compositeDisposable.clear()
}
}
BindingAdapter :
#BindingAdapter("visibleGone")
fun View.visibleGone(visible: Boolean) {
visibility = if (visible) View.VISIBLE else View.GONE
}
Not sure if this is what you were looking for..?
android:visibility="#{vm.isFeedsLoading} ? View.VISIBLE : View.GONE"
https://stackoverflow.com/a/47746579/7697633
Related
I am new to Android Development. I like to show Bottom Sheet Dialog Fragment when I click one of the menu buttons at the Bottom Navigation View. When I click the favorite button on the languages, it shows empty for the bottom sheet dialog. Is there other way to do it? Thanks.
Main Activity
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
replacementFragment(HomeFragment())
binding.bottomNavigationView.setOnItemSelectedListener {
when (it.itemId) {
R.id.home -> replacementFragment(HomeFragment())
R.id.language -> replacementFragment(LanguageFragment())
// I cannot show the list on the fragment
R.id.favourite -> showFavouriteBottomFragment()
}
true
}
// val bottomSheetFragment = FavouriteFragment()
// bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.getTag() )
}
private fun replacementFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragmentContainerView, fragment)
fragmentTransaction.commit()
}
// I cannot get the following part to work....
private fun showFavouriteBottomFragment() {
val bottomSheetFragment = FavouriteFragment()
bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.getTag() )
}
}
Favourite Fragment
#AndroidEntryPoint
class FavouriteFragment: BottomSheetDialogFragment(){
private var _binding: FavouriteBottomSheetBinding ?= null
private val binding get() = _binding!!
private val viewModel: FavouriteViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FavouriteBottomSheetBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// val binding = FavouriteBottomSheetBinding.bind(view)
val favouriteAdapter = FavouriteAdapter()
binding.apply {
favouriteLanguageList.apply{
adapter = favouriteAdapter
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
}
}
viewModel.favouriteLanguage.observe(viewLifecycleOwner){
favouriteAdapter.submitList(it)
}
}
}
Bottom Menu
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/home"
android:icon="#drawable/ic_baseline_home_24"
android:title="Home"
tools:ignore="HardcodedText" />
<item
android:id="#+id/favourite"
android:icon="#drawable/ic_baseline_favorite_24"
android:title="Favourite"
tools:ignore="HardcodedText" />
<item
android:id="#+id/language"
android:icon="#drawable/ic_baseline_language_24"
android:title="Language"
tools:ignore="HardcodedText" />
</menu>
AppModule
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
fun provideTestString() = "This is a string we will inject"
#Provides
#Singleton
fun provideDatabase(
app: Application,
callback: LanguageDatabase.Callback
) = Room.databaseBuilder(app, LanguageDatabase::class.java, "language_database")
.fallbackToDestructiveMigration()
.addCallback(callback)
.build()
#Provides
fun provideLanguageDao(db: LanguageDatabase) = db.languageDao()
#ApplicationScope
#Provides
#Singleton
fun provideApplicationScope() = CoroutineScope(SupervisorJob())
}
#Retention(AnnotationRetention.RUNTIME)
#Qualifier
annotation class ApplicationScope
Favourite Adapter
class FavouriteAdapter : ListAdapter<Language, FavouriteAdapter.FavouriteViewAHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavouriteViewAHolder {
val binding = ItemFavouriteBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return FavouriteViewAHolder(binding)
}
override fun onBindViewHolder(holder: FavouriteViewAHolder, position: Int) {
val currentItem = getItem(position)
holder.bind(currentItem)
}
inner class FavouriteViewAHolder(private val binding: ItemFavouriteBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(language: Language){
binding.apply {
rbIsClicked.isChecked = language.isChecked
tvFavouriteLanguage.text = language.language
}
}
}
class DiffCallback : DiffUtil.ItemCallback<Language>() {
override fun areItemsTheSame(oldItem: Language, newItem: Language) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Language, newItem: Language) = oldItem == newItem
}
}
Favourite View Model
#HiltViewModel
class FavouriteViewModel #Inject constructor(
private val languageDao: LanguageDao,
) : ViewModel() {
val favouriteLanguage = languageDao.getFavouriteLanguageByName().asLiveData()
}
Preferences Manager
private const val TAG = "PreferencesManager"
private val Context.dataStore by preferencesDataStore("user_preferences")
enum class SortOrder { BY_NAME}
data class FilterPreferences(val sortOrder: SortOrder, val hideSelectedLanguage: Boolean, val selectedLanguage: String)
#Singleton
class PreferencesManager #Inject constructor(#ApplicationContext context: Context){
private val dataStore = context.dataStore
val preferencesFlow = dataStore.data
.catch { exception ->
if(exception is IOException){
Log.e(TAG, "Error reading preferences", exception)
emit(emptyPreferences())
}else{
throw exception
}
}
.map{ preferences ->
val sortOrder = SortOrder.valueOf(
preferences[PreferencesKeys.SORT_ORDER] ?:SortOrder.BY_NAME.name
)
val hideSelectedLanguage = preferences[PreferencesKeys.HIDE_SELECTED_LANGUAGE] ?: false
val selectedLanguage = preferences[PreferencesKeys.SELECTED_LANGUAGE]?: "English"
FilterPreferences(sortOrder, hideSelectedLanguage, selectedLanguage)
}
suspend fun updateSortOrder(sortOrder: SortOrder){
dataStore.edit { preferences ->
preferences[PreferencesKeys.SORT_ORDER] = sortOrder.name
}
}
suspend fun updateHideSelectedLanguage(hideSelectedLanguage: Boolean){
dataStore.edit { preferences ->
preferences[PreferencesKeys.HIDE_SELECTED_LANGUAGE] = hideSelectedLanguage
}
}
suspend fun updateSelectedLanguage(selectedLanguage: String){
dataStore.edit{ preferences ->
preferences[PreferencesKeys.SELECTED_LANGUAGE] = selectedLanguage
}
}
private object PreferencesKeys{
val SORT_ORDER = stringPreferencesKey("sort_order")
val HIDE_SELECTED_LANGUAGE = booleanPreferencesKey("hide_selected_language")
val SELECTED_LANGUAGE = stringPreferencesKey("selected_language")
}
}
I have a Language Fragment which looks similar to Favourite Fragment.
I find a temporary solution...but I still have a bug as I have the press the favourite button twice to show a list in the BottomSheetDialogFragment.
Is there a way, I can solve the problem? The first time I press the favourite button it doesn't shows a BottomSheetDialog fragment.... I have to press it again to show the list.
KC
In the MainActivity, I declare the class favouriteFragment.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// declare the favroute fragment.
val favouriteFragment = FavouriteFragment()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
replacementFragment(HomeFragment())
binding.bottomNavigationView.setOnItemSelectedListener {
when (it.itemId) {
R.id.home -> replacementFragment(HomeFragment())
R.id.language -> replacementFragment(LanguageFragment())
R.id.favourite ->
// Pass the instace in the function.
showFavouriteBottomFragment(favouriteFragment)
}
true
}
In the function in Main Activity
private fun showFavouriteBottomFragment(favouriteFragment: FavouriteFragment) {
favouriteFragment.show(supportFragmentManager, favouriteFragment.tag)
}
I remove "setHasFixedSize(true)" and it can load the first time when I press the favorite button.
The LiveData is supposed to print a String in playerOneName text attribute that we took from the TextInputEditText earlier but I tried even in Log.d it shows no result at all.
Here's the code
ViewModel
class GameViewModel: ViewModel() {
private val _playerOne = MutableLiveData<String>()
val playerOne: LiveData<String> = _playerOne
private val _playerTwo = MutableLiveData<String>()
val playerTwo: LiveData<String> = _playerTwo
private val _playerOneScore = MutableLiveData<Int>(0)
val playerOneScore: LiveData<Int> = _playerOneScore
private val _playerTwoScore = MutableLiveData<Int>(0)
val playerTwoScore: LiveData<Int> = _playerTwoScore
fun setPlayerOne(name: String) {
_playerOne.value = name
}
fun setPlayerTwo(name: String) {
_playerTwo.value = name
}
}
XML of StartFragment
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/player_one_name"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:hint="#string/player_one_name"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#id/start_img">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/edit_player_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1" />
XML for GameFragment
<TextView
android:id="#+id/player_one_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="#{viewModel.playerOne.toString()}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
GameFragment
class GameFragment : Fragment() {
private var _binding: FragmentGameBinding? = null
private val binding get() = _binding!!
private val sharedViewModel: GameViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentGameBinding.inflate(inflater, container, false)
binding.resetBtn.setOnClickListener {
findNavController().navigate(R.id.action_gameFragment_to_startFragment)
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
gameFragment = this#GameFragment
}
Log.d("GameFragment", "Player One:" + sharedViewModel.playerOne.toString())
}
}
I tried playerOneScore but as i expected the is the same problem here's the code for Log.d in the viewModel class
Log.d("GameViewModel", _playerOneScore.value.toString() + " Score: " + playerOneScore.value)
and here's the logcat
D/GameViewModel: 0 Score: null
Try following Code I hope your problem will be solved
Gradle file
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
ViewModel
class GameViewModel: ViewModel() {
private val _playerOne = MutableLiveData<String>()
val playerOne: LiveData<String> = Transformations.map(_playerOne) { it }
private val _playerTwo = MutableLiveData<String>()
val playerTwo: LiveData<String> = Transformations.map(_playerTwo) { it }
private val _playerOneScore = MutableLiveData<Int>(0)
val playerOneScore: LiveData<Int> = Transformations.map(_playerOneScore) { it }
private val _playerTwoScore = MutableLiveData<Int>(0)
val playerTwoScore: LiveData<Int> = Transformations.map(_playerTwoScore) { it }
fun setPlayerOne(name: String) {
_playerOne.value = name
}
fun setPlayerTwo(name: String) {
_playerTwo.value = name
}
}
GameFragment
class GameFragment : Fragment() {
private lateinit var sharedViewModel: GameViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedViewModel= ViewModelProvider(this).get(GameViewModel::class.java)
sharedViewModel.playerOne.observe(viewLifecycleOwner) {
Log.d("GameFragment", "Player One: $it")
}
// use this method to change the value
sharedViewModel.setPlayerOne("Some Player")
}
in the StartFragment.kt you need to override onPause() function so that when you go to the next fragment it saves the name of the the players in the ViewModel.
StartFragment.kt
override fun onPause() {
super.onPause()
var playerOneName: String = binding.editPlayerOne.text.toString()
sharedViewModel.setPlayerOne(playerOneName)
var playerTwoName: String = binding.editPlayerTwo.text.toString()
sharedViewModel.setPlayerTwo(playerTwoName) }
For the player's score i did the following
ViewModel
init {
resetData()
}
fun playerOneWon() {
_playerOneScore.value = _playerOneScore.value?.plus(1)
}
fun playerTwoWon() {
_playerTwoScore.value = _playerTwoScore.value?.plus(1)
fun resetData() {
_playerOneScore.value = 0
_playerTwoScore.value = 0 }
I used the playerOneWon() and playerTwoWon() functions to increment the score in the GameFragment.kt and for the xml i used
android:text="#{viewModel.playerOneScore.toString()}"
How to handle android rotate screen if I have a fragment that observe the data from view model to fill recyclerview list ?!
when rotation happened the data lost, so how can I handle it with viewmodel so the user didn't feel any different after rotation?
this is the part of the view model the return data:
private fun getImageList(keyWord: String) {
responseManager.loading()
val disposable = imageListUseCase.execute(keyWord, { success ->
responseManager.hideLoading()
_observeImageListData.value = Event(success)
}, { error ->
responseManager.failed(error)
})
compositeDisposable.add(disposable)
}
and here I set the recycler data on the fragment:
UPDATE
#AndroidEntryPoint
class ImagePickerFragment : Fragment() {
private lateinit var imagePickerBinding: FragmentImagePickerBinding
private val imagePickerViewModel: ImagePickerViewModel by activityViewModels()
private lateinit var imagePickerAdapter: ImagePickerAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
imagePickerBinding = FragmentImagePickerBinding.inflate(inflater, container, false)
observeImageList()
observeSearchText()
return imagePickerBinding.root
}
private fun observeImageList() {
imagePickerViewModel.observeImageListData.observe(
viewLifecycleOwner,
EventObserver { imageList ->
imagePickerAdapter = ImagePickerAdapter(imageList, imagePickerViewModel)
imagePickerBinding.apply {
rvImageList.setHasFixedSize(true)
rvImageList.layoutManager = GridLayoutManager(requireContext(),2)
rvImageList.adapter = imagePickerAdapter
}
})
}
Any tips?
UPDATE :
The viewModel extends from BaseViewModel :
abstract class BaseViewModel: ViewModel() {
val compositeDisposable = CompositeDisposable()
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
}
and this is the full viewmodel:
#HiltViewModel
class ImagePickerViewModel #Inject constructor(
private val imageListUseCase: ImageListUseCase,
private val responseManager: ResponseManager
) : BaseViewModel() {
private val _observeImageListData = MutableLiveData<Event<ArrayList<Image>>>()
init {
getImageList(Constants.KEY_WORD)
}
private fun getImageList(keyWord: String) {
responseManager.loading()
val disposable = imageListUseCase.execute(keyWord, { success ->
responseManager.hideLoading()
_observeImageListData.value = Event(success)
}, { error ->
responseManager.failed(error)
})
compositeDisposable.add(disposable)
}
fun filterSearchKeyWord(filteredKeyWord: String) {
if(filteredKeyWord.isNotEmpty())
getImageList(filteredKeyWord)
}
//getters:
val observeImageListData: LiveData<Event<ArrayList<Image>>>
get() = _observeImageListData
}
In your manifest, try putting this in your activity tag:
android:configChanges="orientation"
So it looks like:
<activity
android:name=".MainActivity"
android:configChanges="orientation"
android:exported="true">
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.
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}