I would like to ask you for help. I am writing an application that uses MVVM and LiveData architecture. Inside ViewPager I have 3 fragments displaying data that comes from ViewModel. And I noticed that after connecting the viewModel to the activity and to the fragment, the data is updated only when the activity is started, but then Observe does not update the data even though the data has changed. After calling the next query to the server, inside onDataSet I send the appropriate time and obtains JSON data from the server, which it parses and passes to ViewModel. Why Fragment updates data only once in the beginning and nothing changes after?
This is the activity that hosts the fragments
class MainActivity : AppCompatActivity(), DatePickerDialog.OnDateSetListener {
private lateinit var currencyViewModel: CurrencyViewModel
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private lateinit var navigationView: NavigationView
private lateinit var floatingActionButton: FloatingActionButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fm = supportFragmentManager
currencyViewModel = ViewModelProvider
.AndroidViewModelFactory(application)
.create(CurrencyViewModel::class.java)
viewPager = findViewById(R.id.viewPager)
tabLayout = findViewById(R.id.tabLayout)
navigationView = findViewById(R.id.navigationView)
floatingActionButton = findViewById(R.id.floatingActionButton)
val viewPagerAdapter = CurrencyViewPagerAdapter(this)
viewPager.adapter = viewPagerAdapter
TabLayoutMediator(tabLayout
,viewPager
,TabLayoutMediator.TabConfigurationStrategy {
tab, position ->
when(position){
0 -> tab.text = "Tabela A"
1 -> tab.text = "Tabela B"
2 -> tab.text = "Tabela C"
}
}).attach()
floatingActionButton.setOnClickListener {
val dialog = CalendarFragment()
dialog.show(fm, "DatePickerDialog")
}
}
override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
//Convert year,month,day to millisecounds
val c = Calendar.getInstance()
c.set(year,month,dayOfMonth)
val dayInMillis = c.time.time
val today = Calendar.getInstance()
if(checkIsDateAfterToday(today, c)){
CoroutineScope(Dispatchers.Main).launch {
currencyViewModel.setTableA(dayInMillis)
}
}
}
This is ViewModel common for activity and fragment
class CurrencyViewModel : ViewModel() {
private val repository = CurrencyRepository()
val tableA: MutableLiveData<Array<TableA>> by lazy {
MutableLiveData<Array<TableA>>().also {
loadTableA(Date().time)
}
}
private fun loadTableA(time: Long) {
CoroutineScope(Dispatchers.Main).launch {
val loadedData = CoroutineScope(Dispatchers.IO).async {
repository.getTableA(time)
}.await()
tableA.value = loadedData
}
}
fun setTableA(time: Long){
loadTableA(time)
}
}
And that's the fragment which displays data in recyclerView
class TableAFragment : Fragment() {
private lateinit var currencyViewModel: CurrencyViewModel
private lateinit var recyclerViewA: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_table_a, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
currencyViewModel = ViewModelProvider.AndroidViewModelFactory
.getInstance(requireActivity().application)
.create(CurrencyViewModel::class.java)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerViewA = view.findViewById(R.id.recyclerView_A)
recyclerViewA.layoutManager = LinearLayoutManager(requireContext())
currencyViewModel.tableA.observe(viewLifecycleOwner, androidx.lifecycle.Observer{
val nbpAdapter = NBPAdapter(it)
recyclerViewA.adapter = nbpAdapter
})
}
}
Your instantiation of ViewModel is incorrect.
Should be
currencyViewModel = ViewModelProvider(this).get<CurrencyViewModel>() // lifecycle-ktx
and in Fragment:
currencyViewModel = ViewModelProvider(requireActivity()).get<CurrencyViewModel>() // lifecycle-ktx
Related
i have a really simple vocabulary note app contains 2 fragment and 1 root activity. In HomeFragment i have a button "addVocabularyButton". When it is clicked a BottomSheetDialogFragment appears and user gives 3 inputs and with a viewmodel it is saved in DB. My problem is when i save the input to the DB it works fine but i cannot see in HomeFragment that word instantaneously. I have to re-run the app to see in home fragment. I am using Navigation library and recycler view in home fragment.
Github link : https://github.com/ugursnr/MyVocabularyNotebook
Home Fragment
class HomeFragment : Fragment() {
private var _binding : FragmentHomeBinding? = null
private val binding get() = _binding!!
private var vocabularyAdapter = VocabulariesHomeAdapter()
private lateinit var sharedViewModel: AddVocabularySharedViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(layoutInflater,container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//sharedViewModel = ViewModelProvider(this)[AddVocabularySharedViewModel::class.java]
sharedViewModel = (activity as MainActivity).sharedViewModel
sharedViewModel.getAllVocabulariesFromDB()
observeAllVocabularies()
prepareRecyclerView()
addVocabularyOnClick()
vocabularyAdapter.onItemDeleteClicked = {
sharedViewModel.deleteVocabulary(it)
observeAllVocabularies()
}
}
private fun prepareRecyclerView(){
binding.recyclerViewHome.apply {
layoutManager = LinearLayoutManager(context)
adapter = vocabularyAdapter
}
}
private fun addVocabularyOnClick(){
binding.addVocabularyButton.setOnClickListener{
val action = HomeFragmentDirections.actionHomeFragmentToAddVocabularyBottomSheetFragment()
Navigation.findNavController(it).navigate(action)
}
}
private fun observeAllVocabularies(){
sharedViewModel.allVocabulariesLiveData.observe(viewLifecycleOwner, Observer {
vocabularyAdapter.updateVocabularyList(it)
})
}
}
Dialog Fragment
class AddVocabularyBottomSheetFragment : BottomSheetDialogFragment() {
private var _binding : FragmentAddVocabularyBottomSheetBinding? = null
private val binding get() = _binding!!
private lateinit var sharedViewModel: AddVocabularySharedViewModel
private var vocabularyInput : String? = null
private var translationInput : String? = null
private var sampleSentenceInput : String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAddVocabularyBottomSheetBinding.inflate(layoutInflater,container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//sharedViewModel = ViewModelProvider(this)[AddVocabularySharedViewModel::class.java]
sharedViewModel = (activity as MainActivity).sharedViewModel
binding.addOrUpdateVocabularyButton.setOnClickListener {
vocabularyInput = binding.vocabularyActualET.text.toString()
translationInput = binding.vocabularyTranslationET.text.toString()
sampleSentenceInput = binding.vocabularySampleSentenceET.text.toString()
val inputVocabulary = Vocabulary(vocabularyInput,translationInput,sampleSentenceInput)
insertVocabularyToDB(inputVocabulary)
sharedViewModel.getAllVocabulariesFromDB()
dismiss()
}
}
private fun insertVocabularyToDB(vocabulary: Vocabulary){
sharedViewModel.insertVocabulary(vocabulary)
}
}
Shared ViewModel
class AddVocabularySharedViewModel(application: Application) : AndroidViewModel(application) {
private var _allVocabulariesLiveData = MutableLiveData<List<Vocabulary>>()
private var _vocabularyLiveData = MutableLiveData<Vocabulary>()
val allVocabulariesLiveData get() = _allVocabulariesLiveData
val vocabularyLiveData get() = _vocabularyLiveData
val dao = VocabularyDatabase.makeDatabase(application).vocabularyDao()
val repository = VocabularyRepository(dao)
fun insertVocabulary(vocabulary: Vocabulary) = CoroutineScope(Dispatchers.IO).launch {
repository.insertVocabulary(vocabulary)
}
fun updateVocabulary(vocabulary: Vocabulary) = CoroutineScope(Dispatchers.IO).launch {
repository.updateVocabulary(vocabulary)
}
fun deleteVocabulary(vocabulary: Vocabulary) = CoroutineScope(Dispatchers.IO).launch {
repository.deleteVocabulary(vocabulary)
}
fun getAllVocabulariesFromDB() = CoroutineScope(Dispatchers.IO).launch {
val temp = repository.getAllVocabulariesFromDB()
withContext(Dispatchers.Main){
_allVocabulariesLiveData.value = temp
}
}
fun getVocabularyDetailsByID(vocabularyID : Int) = CoroutineScope(Dispatchers.IO).launch {
val temp = repository.getVocabularyDetailsByID(vocabularyID).first()
withContext(Dispatchers.Main){
_vocabularyLiveData.value = temp
}
}
}
Adapter
class VocabulariesHomeAdapter : RecyclerView.Adapter<VocabulariesHomeAdapter.VocabulariesHomeViewHolder>() {
lateinit var onItemDeleteClicked : ((Vocabulary) -> Unit)
val allVocabulariesList = arrayListOf<Vocabulary>()
class VocabulariesHomeViewHolder(val binding : RecyclerRowBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VocabulariesHomeViewHolder {
return VocabulariesHomeViewHolder(RecyclerRowBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: VocabulariesHomeViewHolder, position: Int) {
val vocabulary = allVocabulariesList[position]
holder.binding.apply {
actualWordTV.text = vocabulary.vocabulary
translationWordTV.text = vocabulary.vocabularyTranslation
deleteButtonRV.setOnClickListener {
onItemDeleteClicked.invoke(vocabulary)
notifyItemRemoved(position)
}
}
}
override fun getItemCount(): Int {
return allVocabulariesList.size
}
fun updateVocabularyList(newList : List<Vocabulary>){
allVocabulariesList.clear()
allVocabulariesList.addAll(newList)
notifyDataSetChanged()
}
}
I know there are lots of codes up there but i have a really big problems about using these dialog fragments. Thank you for your help.
This is because multiple instances of the same View Model are created by the Navigation Library for each Navigation Screen.
You need to tell the Navigation Library to share the same ViewModel between all navigation screens.
Easiest way to fix this is to scope the viewModel to the Activity rather than a Fragment and using it in all your fragments.
val viewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
This way, the viewModel is scoped to the Application instance rather than Fragment. This will keep the state in the viewModel persistent across the Application.
You can also do this by scoping the viewModel to the navigation graph.
val myViewModel: MyViewModel by navGraphViewModels(R.id.your_nested_nav_id)
Alternate method, if you're using dependency injection libraries
val navController = findNavController();
val navBackStackEntry = navController.currentBackStackEntry!!
If you use hilt, you can just pass your NavBackStackEntry of the NavGraph to hiltViewModel()
val viewModel = hiltViewModel<MyViewModel>(//pass NavBackStackEntry)
This will give you a viewModel that is scoped to NavBackStackEntry and will only be recreated when you pop the NavBackStackEntry(ie Navigate out of the navigation screens.)
Project1
I have created a app that scans nearby wifidirect enabled devices whose UI was simple and had only one layout(activitymain.xml) and the code was in MainActivity.java & WifiDirectBroadcastReceiver. (Code can be found here: Can't find nearby WiFi- Direct devices showing "No Device Found!")
Project2
Now, I want to use Tablayout(custom not from default) which contains 2 tabs so I have to use 2 fragments.
Where should I place the code that was in MainActivity(project1)?
should I copy to fragment1 or MainActivity(Project2)
You have to copy the code of MainActivity (project1) to fragment of the tabbed layout. And then configure the SectionPagerAdapter like below.
Also you have to change some code of your MainActivity so that it gets fitted into the fragment.
private val TAB_TITLES = arrayOf(
R.string.tab_text_1,
R.string.tab_text_2
)
/**
* A [FragmentPagerAdapter] that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
class SectionsPagerAdapter(private val context: Context, fm: FragmentManager)
: FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
var fragment: Fragment? = null
when (position) {
0 -> fragment = Fragment1("f1","f1")
1 -> fragment = Fragment2("f2","f2")
}
return fragment!!
}
override fun getPageTitle(position: Int): CharSequence? {
return context.resources.getString(TAB_TITLES[position])
}
override fun getCount(): Int {
// Show 2 total pages.
return 2
}
}
You can create fragments like this:
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class Fragment1 : Fragment() {
private var param1: String? = null
private var param2: String? = null
private var _binding: Fragment1Binding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onPause() {
super.onPause()
}
override fun onResume() {
super.onResume()
}
companion object {
#JvmStatic
fun newInstance(param1: String, param2: String) =
HomeFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
You can make Fragment2 like this and attach this to your tabbed layout.
I want to pass data to fragment so when items in recycler adapter clicked it pass item name (sample[position].text1) fetched from firebase to fragment. I tried bundle, interface but getting error in both methods.I searched on internet but not find anything which solve my problem. mainActivity(splash screen) is only Activity in my App rest are fragments.
I used inner class method, I'm getting result but in another fragment where this adapter attached and I don't want it there.
Problem: pass sample[position].text1 to fragment so I can pass it to db.collection("here") to fetch data from Firebase.
Adapter
class dashboard_gridlayout_adapter(
private val sampledata: ArrayList<daxhboard_gridlayout_data>
): Adapter<dashboard_gridlayout_adapter.dashboard_viewholder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): dashboard_viewholder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.dashboard_gridlayout_single_item_design, parent, false)
return dashboard_viewholder(itemView)
}
override fun onBindViewHolder(holder: dashboard_viewholder, position: Int) {
Glide.with(holder.itemView).load(sampledata[position].imageResource)
.placeholder(R.drawable.ic_baseline_history_icon)
.into(holder.imageView)
holder.textView.text = sampledata[position].text1
holder.itemView.setOnClickListener {
val appCompatActivity = it.context as AppCompatActivity
appCompatActivity.supportFragmentManager.beginTransaction()
.replace(R.id.Activity_frag_container, service_providers_list())
.addToBackStack(null)
.commit()
}
}
override fun getItemCount() = sampledata.size
inner class dashboard_viewholder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.dashboard_adapter_image
val textView: TextView = itemView.dashboard_adapter_text
}
}
Fragment
class service_providers_list : Fragment(){
private var db = FirebaseFirestore.getInstance()
private lateinit var service_list_recycler: RecyclerView
var servlist = ArrayList<service_provider_list_data>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.service_providers_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getserviceproviderdata()
service_list_recycler = service_provider_recycle_view.findViewById(R.id.service_provider_recycle_view)
service_provider_recycle_view.layoutManager = LinearLayoutManager(this.requireContext())
service_provider_recycle_view.setHasFixedSize(true)
}
private fun getserviceproviderdata() {
db.collection("Barber").orderBy("dist")
.get()
.addOnSuccessListener { documents ->
servlist.clear()
for (document in documents) {
val imgurl = document.data["imageResource"].toString()
val prov_name = document.data["provider_name"].toString()
val prov_address = document.data["provider_address"].toString()
val prov_rate = document.data["provider_rating"].toString()
val prov_dist = document.data["provider_distance"].toString()
servlist.add(service_provider_list_data(imgurl, prov_name, prov_address, prov_rate, prov_dist))
service_provider_recycle_view.adapter = service_provider_list_adapter(servlist)
}
}
.addOnFailureListener { exception ->
Log.e("serf", "Error getting documents: ", exception)
}
}
}
MainActivity (It's a splash screen)
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
#Suppress("DEPRECATION")
Handler().postDelayed(
{
supportFragmentManager.beginTransaction().replace(R.id.Activity_frag_container,Login_Screen()).commit()
},
1500
)
}
}
I solved this problem
just add new parameter(need to pass) inside replace in adapter
holder.itemView.setOnClickListener {
val datashares = sampledata[position].text1
val appCompatActivity = it.context as AppCompatActivity
appCompatActivity.supportFragmentManager.beginTransaction()
.replace(R.id.Activity_frag_container, service_providers_list(datashares))
.addToBackStack(null)
.commit()
}
and inside fragment just add
class service_providers_list(datashares: String) Fragment(){
//variable declaration
private var datasharae = datashares
(inside function where i wnt to add code i.e getserviceproviderdata() )
fun getserviceproviderdata() {
db.collection(datasharae)
.............
..............
......rest code.....
.........}
I have a ShopFilterFragmentProductFilter which is inside a ShopFilterFragmentHolder which itself holds a ViewPager2. This ShopFilterFragmentHolder is a DialogFragment which is opened inside my ShopFragment. So ShopFragment -> ShopFilterFragmentHolder (Dialog, ViewPager2) -> ShopFilterFragmentProductFilter. ALL of these Fragments should share the same navgraphscoped viewmodel.
The problem I have is, that when I attach an observer inside my ShopFilterFragmentProductFilter to get my recyclerview list from cloud-firestore, this observer never gets called and therefore I get the error message "No Adapter attached, skipping layout". I know that this is not a problem with how I instantiate and assign the adapter to my recyclerview, because when I set a static list (e.g creating a list inside my ShopFilterFragmentProductFilter) everything works.
Why do I don't get the livedata value? To my mind, there is a problem with the viewmodel creation.
Here is my current approach:
ShopFilterFragmentProductFilter
#AndroidEntryPoint
class ShopFilterFragmentProductFilter : Fragment() {
private var _binding: FragmentShopFilterItemBinding? = null
private val binding: FragmentShopFilterItemBinding get() = _binding!!
private val shopViewModel: ShopViewModel by navGraphViewModels(R.id.nav_shop) { defaultViewModelProviderFactory }
#Inject lateinit var shopFilterItemAdapter: ShopFilterItemAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentShopFilterItemBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindObjects()
submitAdapterList()
}
override fun onDestroyView() {
super.onDestroyView()
binding.rvShopFilter.adapter = null
_binding = null
}
private fun bindObjects() {
with(binding) {
adapter = shopFilterItemAdapter
}
}
private fun submitAdapterList() {
shopViewModel.shopProductFilterList.observe(viewLifecycleOwner) {
shopFilterItemAdapter.submitList(it)
shopFilterItemAdapter.notifyDataSetChanged()
toast("SUBMITTED LIST") // this does never get called
}
/* // this works
shopFilterItemAdapter.submitList(
listOf(
ShopFilterItem(0, "ITEM 1"),
ShopFilterItem(0, "ITEM 2"),
ShopFilterItem(0, "ITEM 3"),
ShopFilterItem(0, "ITEM 4"),
ShopFilterItem(0, "ITEM 5"),
)
)
*/
}
}
ViewModel
class ShopViewModel #ViewModelInject constructor(
private val shopRepository: ShopRepository,
private val shopFilterRepository: ShopFilterRepository
) : ViewModel() {
private val query = MutableLiveData(QueryHolder("", ""))
val shopPagingData = query.switchMap { query -> shopRepository.search(query).cachedIn(viewModelScope) }
val shopProductFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getProductFilterList() }
val shopListFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getListFilterList() }
fun search(newQuery: QueryHolder) {
this.query.value = newQuery
}
}
ShopFilterRepositoryImpl
class ShopFilterRepositoryImpl #Inject constructor(private val db: FirebaseFirestore) : ShopFilterRepository {
override suspend fun getProductFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.productFilter
override suspend fun getListFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.listFilter
}
Nav_graph
Probably, you should define it as MutableLiveData:
private val shopProductFilterList: MutableLiveData<List<ShopFilterItem>> = MutableLiveData()
And in a method in your viewModel that gets the data through repository, you should post the LiveData value:
fun getProductFilterList() = viewModelScope.launch {
val dataFetched = repository. getProductFilterList()
shopProductFilterList.postValue(dataFetched)
}
I'm using Navigation Component, although I don't think that's the problem. The thing is that when I'm in a fragment that contains a ViewPager and I navigate to another one, when I go back using the back button or the gesture of the mobile phone, it returns to the previous fragment but it stops showing the ViewPager. I'll leave you my code for that fragment:
class HomeFragment : Fragment() {
private lateinit var homeFragmentViewModel: HomeFragmentViewModel
private var listAdapter: FlagsListAdapter? = null
private var regionName: String? = null
private val hashtagLabel: TextView by lazy { home_fragment__label__hashtag }
private val flagViewPager: ViewPager by lazy { home_fragment__viewpager__countries }
private val countryLabel: TextView by lazy { home_fragment__label__country_name }
private val showCasesButton: Button by lazy { home_fragment__button__country_cases }
companion object {
fun newInstance(): HomeFragment {
return HomeFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeFragmentViewModel = ViewModelProvider(this).get(HomeFragmentViewModel::class.java)
homeFragmentViewModel.getCountriesFlagLiveData().observeOnce(viewLifecycleOwner, Observer {
setFlagsAdapter(it)
})
showCasesButton.setOnClickListener {
val actionNavigateToShowCasesFragment = HomeFragmentDirections.navigateHomeFragmentToShowCasesFragment()
regionName?.let { regionName -> actionNavigateToShowCasesFragment.regionName = regionName }
it.findNavController().navigate(actionNavigateToShowCasesFragment)
}
setFormatHashtag()
}
private fun setFlagsAdapter(flagModelList: List<FlagModel>) {
listAdapter = context?.let {
FlagsListAdapter(
flagModelList,
it
)
}
flagViewPager.adapter = listAdapter
flagViewPager.setPadding(130, 0, 130, 0)
flagViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
Toast.makeText(GlobalApplication.getContextFromApplication, "Hola", Toast.LENGTH_SHORT).show()
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
countryLabel.text = ""
countryLabel.text = flagModelList[position].regionName
regionName = flagModelList[position].regionName
}
override fun onPageSelected(position: Int) {
countryLabel.text = flagModelList[position].regionName }
})
}
private fun setFormatHashtag() {
val text = getString(R.string.home_fragment_hashtag)
val spannableString = SpannableString(text)
val foregroundColorSpan = context?.let {
ForegroundColorSpan(ContextCompat.getColor(it, R.color.hashtagColor))
}
spannableString.setSpan(foregroundColorSpan, 0, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
hashtagLabel.text = spannableString
}
}
This is my activity:
class MainActivity : AppCompatActivity() {
private val navigationBottomBar by lazy { activity_main__navigation_view__bottom_bar }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setUpNavigation()
}
private fun setUpNavigation() {
val navController = Navigation.findNavController(this, R.id.activity_main__graph__nav_host)
NavigationUI.setupWithNavController(navigationBottomBar, navController)
}
}
When you load the fragment the first time it is shown like this, which is how it should be shown, and if I use the Bottom Navigation View it does well too:
But when I use the back button on my phone, here's what happens:
The problem is in your HomeFragment,setFlagsAdapter(it)` doesn't get called when you come back to this fragment. Either
change observeOnce to observe
or
Move
homeFragmentViewModel.getCountriesFlagLiveData().observeOnce(viewLifecycleOwner, Observer {
setFlagsAdapter(it)
})
from onCreateView to
override fun onResume(){
super.onResume()
homeFragmentViewModel.getCountriesFlagLiveData().observeOnce(viewLifecycleOwner, Observer {
setFlagsAdapter(it)
})
}