Double fragment instance when it comes from background - kotlin

I've got the problem with creating multiple fragment instances in OnCreate. When i close the app using Home button and I will return to the app, the fragment instance is created one more time. How can I prevent this?
fragment = FragmentMain.newInstance(intent.extras?.getSerializable(DATA_MAIN)).also {
supportFragmentManager.beginTransaction()
.add(frameLayout.id, it, FragmentMain::class.java.simpleName)
.addToBackStack(FragmentMain::class.java.simpleName)
.commit()
}

This is expected behavior, as Android recreates the fragments after process death that are added to the fragment manager.
You're just also adding a 2nd new fragment on top of the one created by Android, which, you probably don't want to do.
fragment = when {
savedInstanceState == null -> FragmentMain.newInstance(intent.extras?.getSerializable(DATA_MAIN)).also {
supportFragmentManager.beginTransaction()
.add(frameLayout.id, it, FragmentMain::class.java.simpleName)
.addToBackStack(FragmentMain::class.java.simpleName)
.commit()
}
else -> supportFragmentManager.findFragmentByTag(FragmentMain::class.java.simpleName)
}

Related

I'm trying to take bundles on my HomeFragment but when i enter first, i got error

I'm sending bundles to my Home Fragment at another fragments. But when the app opens at the first, gives me an error because app didnt takes any bundles at the first. By the way i'm sending and getting bundles like this;
//Sending
val fragment = Notlar()
val bundle = Bundle()
bundle.putInt("categoryId", -99)
fragment.arguments = bundle
findNavController().navigate(R.id.action_kategoriler_to_notlar, bundle)
//Getting (On Home Fragment)
categoryIdBundle = requireArguments().getInt("categoryId",-1)
I have tried something like;
try {
categoryIdBundle = requireArguments().getInt("categoryId",-1)
} catch (e : Exception) {
categoryIdBundle = -1
}
But even though it opens at the beginning, the bundles i send never come, so the catch block always works. What can i do at this point?
You can try Safe Call Operator in Kotlin .? to make sure if data is not null, and try this code to send data between fragments using bundle
// in the first fragment
findNavController().navigate(
R.id.action_kategoriler_to_notlar,
Bundle().apply {
putInt("categoryId", -99)
}
)
// in destination fragment
arguments?.getInt("categoryId", -1)?.let {
// handle the result here ...
}
you can read more about safe call here, and about send bundle in navigation here
Hope it can help you

NavController in Activity & Fragment Issue

In my Activity I setup the NavController in OnCreate:
navController = findNavController(this, R.id.NavHostFragment)
In my Fragments, I setup the NavController in onViewCreated:
navController = Navigation.findNavController(view)
The Activity only navigates to a few Fragments (e.g. SitesFragment, ContactsFragment, TasksFragment etc) from the NavigationView when item is clicked as below:
R.id.nav_sites_fragment -> {
navController.popBackStack(R.id.sitesFragment, true)
navController.navigate(R.id.sitesFragment)
}
In the Fragments, click events (in RecyclerViews mainly heading to other Fragments, such as SiteFragment, ContactFragment, TaskFragment etc) are handled as below:
if (!navController.popBackStack(R.id.siteFragment, false)) {
// not in BackStack
navController.navigate(R.id.action_contactFragment_to_siteFragment)
}
The problem is, Fragments from Fragment actions remain in the backstack even when I popBackStack in the Activity actions..
I think my understanding of backstack is not correct as I'm not sure what is going on - but that maybe I have created two separate instances of NavController?
EDIT: Although I have looked at this post How to clear navigation Stack after navigating to another fragment in Android, I am still finding same behaviour. PopBackStack in Activity is not clearing Fragments added to the backstack..
For example:
SITES -> SITE -> CONTACT -> CONTACTS should remove first three fragments, but backpress still returns to CONTACT..
Ok, so I found this post https://github.com/android/architecture-components-samples/issues/767.
I modified the code a bit and in my Activity I have the following function:
private fun navigateWithClearStack(destination: Int) {
val navController = findNavController(R.id.NavHostFragment)
val navHostFragment: NavHostFragment = supportFragmentManager.findFragmentById(R.id.NavHostFragment) as NavHostFragment
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.business_navigation)
graph.startDestination = destination
navController.graph = graph
}
Then, where I handle the NavigationView clicks:
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.nav_sites_fragment -> navigateWithClearStack(R.id.sitesFragment)
R.id.nav_projects_fragment -> navigateWithClearStack(R.id.projectsFragment)
R.id.nav_contacts_fragment -> navigateWithClearStack(R.id.contactsFragment)
R.id.nav_tasks_fragment -> navigateWithClearStack(R.id.tasksFragment)
R.id.nav_profile_fragment -> makeToast("Todo: Profile Fragment")
R.id.nav_settings_fragment -> makeToast("Todo: Settings Fragment")
}
business_drawer_layout.closeDrawer(GravityCompat.START)
return true
}
So as users Navigate through app, Fragments are added to the BackStack (using popUpTo to deal with duplicates), but when user clicks shortcut back to one of 'starting' fragments, the graph is replaced, thus clearing the BackStack.. Which I think is kind of neat.

Android RecyclerView NotifyDataSetChanged with LiveData

When instantiating the ListAdapter for my RecyclerView, I call:
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
}
which is what I've seen done in the Android Sunflower project as the proper way to submit LiveData to a RecyclerView. This works fine, and when I add items to my database they are updated in the RecyclerView. However, if I write
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
adapter.notifyDataSetChanged()
}
Then my adapters data observer always lags behind. I call
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver(){
override fun onChanged() {
super.onChanged()
if (adapter.currentList.isEmpty()) {
empty_dataset_text_view.visibility = View.VISIBLE
} else {
empty_dataset_text_view.visibility = View.GONE
}
}
})
And empty_dataset_text_view appears one change after the list size reached zero, and likewise it disappears one change after the list size is non-zero.
Clearly, the observer is running the code before it has submitted it which in this case is LiveData queried from Room. I'd simply like to know what the workaround is for this: is there a way to "await" a return from my LiveData?

how to have loading in Kotlin

my MainActivity contains a ViewPager that loads 4 fragments, each fragment should load lots of data from the server.
so when my app wants to be run for the first time, it almost takes more than 3 seconds and the other times(for example, if you exit the app but not clean it from your 'recently app' window and reopen it) it takes almost 1 second.
while it is loading, it shows a white screen.
is there any way instead of showing a white screen till data become ready, I show my own image?
something like the splash page?
If you do long-running actions on the main thread, you risk getting an ANR crash.
Your layout for each fragment should have a loading view that is initially visible, and your data view. Something like this:
(not code)
FrameLayout
loading_view (can show a progress spinner or something, size is match parent)
content_view (probably a RecyclerView, initial visibility=GONE, size is match parent)
/FrameLayout
You need to do your long running action on a background thread or coroutine, and then swap the visibility of these two views when the data is ready to show in the UI.
You should not be directly handling the loading of data in your Fragment code, as Fragment is a UI controller. The Android Jetpack libraries provide the ViewModel class for this purpose. You would set up your ViewModel something like this. In this example, MyData could be anything. In your case it's likely a List or Set of something.
class MyBigDataViewModel(application: Application): AndroidViewModel(application) {
private val _myBigLiveData = MutableLiveData<MyData>()
val myBigLiveData: LiveData<MyData>() = _myBigLiveData
init {
loadMyBigData()
}
private fun loadMyBigData() {
viewModelScope.launch { // start a coroutine in the main UI thread
val myData: MyData = withContext(Dispatchers.Default) {
// code in this block is done on background coroutine
// Calculate MyData here and return it from lambda
// If you have a big for-loop, you might want to call yield()
// inside the loop to allow this job to be cancelled early if
// the Activity is closed before loading was finished.
//...
return#withContext calculatedData
}
// LiveData can only be accessed from the main UI thread so
// we do it outside the withContext block
_myBigLiveData.value = myData
}
}
}
Then in your fragment, you observe the live data to update the UI when it is ready. The below uses the fragment-ktx library, which you need to add to your project. You definitely should read the documentation on ViewModel.
class MyFragment: Fragment() {
// ViewModels should not be instantiated directly, or they won't be scoped to the
// UI life cycle correctly. The activityViewModels delegate handles instantiation for us.
private val model: MyBigDataViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.myBigLiveData.observe(this, Observer<MyData> { myData ->
loading_view.visibility = View.GONE
content_view.visibility = View.VISIBLE
// use myData to update the view content
})
}
}

How to Block Fragment Re-Creation When BottomNavigationView's Tabs Selected?

I am trying to use a new Navigation structure on my sample project.
I used BottomNavigationView in activity.xml, and it launches with NavigationController.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_launcher)
val navController = Navigation.findNavController(this, R.id.navHostFragment)
NavigationUI.setupWithNavController(bottomNavigation, navController)
}
That's great so far, but every time I click on the tabs, the relative fragments are recreated every time.
How can I prevent this behavior?
I don't want to create new fragments each time.
I just want to use the first created fragments.
Note: I didn't use setOnNavigationItemSelectedListener() or any other listeners. The navigation structure itself regenerates the fragments.
You can prevent creation of new fragment every time by saving the last created fragment instance.
You need to create fragment stack list : val mFragmentStacks: MutableList<Stack<Fragment>>
You need to save fragmnet instance according to tab position : mFragmentStacks[currentStackIndex].push(fragment)
Check first the stack has any entry then attach the last fragment otherwise create new fragment.
if (!mFragmentStacks[index].isEmpty()) {
val fragment = mFragmentStacks[currentStackIndex].peek()
} else {
val fragment = DemoFragment()
mFragmentStacks[currentStackIndex].push(fragment)
}
To avoid the recreation fragment, you can check if there is an instance of this one on the backstack.
You can use the tag of backtask to search after for especific fragment instances