How to prevent showing multiple DialogFragments in Android? - android

I have some custom DialogFragments in my Android application. I would prevent the user from opening multiple dialogs, and I would also avoid disabling the button which shows the dialog once it's pressed or using a variable or it.
So I was trying to make an extension for the fragment which checks for shown fragments via their tags like this:
fun Fragment.isFragmentVisible(tag: String): Boolean {
val fragment = childFragmentManager.findFragmentByTag(tag)
if (fragment != null && fragment.isVisible) {
return true
}
return false
}
And before showing the dialog to check it like this:
binding.destination.editText?.setOnClickListener {
if (!isFragmentVisible(ShopsDialog.TAG)) {
ShopsDialog.newInstance(this, "Destinazione") { bundle ->
val shopId = bundle.getString("shopId")
viewModel.setDestination(shopId)
}
}
}
While the newInstance is:
fun newInstance(fragment: Fragment, title: String, bundle: (Bundle) -> Unit) = ShopsDialog().apply {
arguments = Bundle().apply {
putString(ARG_TITLE, title)
}
fragment.setFragmentResultListener(KEY_CALLBACK_BUNDLE) { requestKey: String, bundle: Bundle ->
if (requestKey == KEY_CALLBACK_BUNDLE) {
bundle(bundle)
}
}
show(fragment.childFragmentManager, TAG)
}
But this seems not to work, I've tried even to use requireActivity().supportFragmentManager instead childFragmentManager but still nothing...

Before showing bottomsheetdialogfragment , i think you can check it like this
if(childFragmentManager.findFragmentByTag(ShopsDialog::class.java.simpleName) == null){
// show your bottom sheet dialog fragment
}
If you are going to show bottomSheetDialogFragment in Fragment then use childFragmentManager and if it is activity then use supportFragmentManager

Related

How to hide views dynamically android?

I receive some user data from server at my app. One of the field which I receive has boolean data type and it changes due to user actions on server. According to value of this variable I have to show/hide a part of my layout. But I don't have enough time for it or I did it in wrong way :( So, first of all I send request at my singleton class and after getting response I save this json field to SharedPreferences:
if (!app_data!!.get("questionnaire").isJsonNull) {
context.getSharedPreferences("app_data", 0).edit().putBoolean("questionnaire", app_data.get("questionnaire").asBoolean).apply()
}
at this time I move to my fragment where I have to change visibility:
val bundle = Bundle()
val personalPage = PersonalPage()
if (sp!!.getBoolean("questionnaire", false)) {
bundle.putBoolean("questionnaire", true)
} else {
bundle.putBoolean("questionnaire", false)
}
personalPage.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.contentContainerT, personalPage).addToBackStack(null).commit()
bottomNavigationView.selectedItemId = R.id.home_screen
and as you can see I send this variable via bundle but as I see it doesn't work good. And at target fragment I try to change visibility:
val bundle = arguments
val cont: RelativeLayout = rootView.findViewById(R.id.polls_container_view)
if (bundle != null) {
if (bundle.getBoolean("questionnaire")) {
if (sp.getBoolean("questionnaire", false) || cont.visibility == View.GONE) {
fragmentManager!!
.beginTransaction()
.detach(PersonalPage())
.attach(PersonalPage())
.commit()
}
cont.visibility = View.VISIBLE
val pollsBtn = rootView.findViewById<Button>(R.id.polls_button)
val pollsInfo = rootView.findViewById<Button>(R.id.polls_info)
pollsBtn.setOnClickListener {
activity!!.supportFragmentManager.beginTransaction().replace(R.id.contentContainerT, PollsScr()).addToBackStack(null).commit()
}
pollsInfo.setOnClickListener {
showPollsInfoMSG()
}
} else {
if (!sp.getBoolean("questionnaire", false) || cont.visibility == View.VISIBLE) {
fragmentManager!!
.beginTransaction()
.detach(PersonalPage())
.attach(PersonalPage())
.commit()
}
cont.visibility = View.GONE
}
}
But I see that when a user changes variable value from true to false and move to this fragment my view is still visible and I have to reload fragment for hiding view. And also when user receive true from server I also have to reload this fragment. I tried to get bool variable at onStart() fun but maybe I don't have enough time for it? Maybe this problem can be solved via broadcast receiver but I don't know how to do it :(

Application Crash/Navigation issue with observer when clicking "back" from fragment

I have a fragment A which sends a search query to the network, and if the result is positive uses Android navigation component to navigate to fragment B, and its done using observers.
After navigation to fragment B, i click on "<-" arrow on the top of the screen, but instead of navigating back to fragment A it reloads fragment B again. And if using the native "back" button on the device, the app crashes with "illegalArgumentException navigation destination unknown" error.
I check the internet for clues on this issue, but all i learned is that this happens because i am using .observe in onViewCreated() and when i go back, it gets called again, and because livedata has something in it already, it just navigates me back to B.
I have tried observing in onActivityCreated(), and using getViewLifeCycleOwner, but no success... the only thing that helped is checking if livedata has observers and returning if true, before using .observe, but it seems incorrect.
This is the viewModel:
private val getAssetResult = MutableLiveData<GeneralResponse<Asset>>()
private val updateAssetResult = MutableLiveData<GeneralResponse<Int>>()
private val deleteAssetResult = MutableLiveData<GeneralResponse<Int>>()
init {
state.value = ViewState(false)
Log.d(TAG, "State in init: $state")
}
fun getAssetResult(): LiveData<GeneralResponse<Asset>>{
return getAssetResult
}
fun findAsset(req: GetAssetRequest) {
scope.launch {
setProgressIndicator(true)
val result = repository.getAsset(req)
getAssetResult.postValue(result)
setProgressIndicator(false)
}
}
This is the fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(EditAssetViewModel::class.java)
setupViewModel()
initFields()
}
private fun setupViewModel() {
if (viewModel.getAssetResult().hasObservers()) // <- This is the part that prevents the app from crashing.
return
viewModel.getAssetResult().observe(this, Observer {
if (it == null) return#Observer
handleSearchResult(it)
})
if (viewModel.getState().hasObservers())
return
viewModel.getState().observe(this, Observer { handleState(it) })
}
private fun handleSearchResult(response: GeneralResponse<Asset>) {
if (response.singleValue == null) {
Toast.makeText(context!!, response.errorMessage, Toast.LENGTH_SHORT).show()
return
}
targetFragment?.let { it ->
val bundle = bundleOf("asset" to response.singleValue)
when(it) {
"UpdateLocation" ->
Navigation.findNavController(view!!).navigate(R.id.updateLocation, bundle)
"EditAsset" -> {
Navigation.findNavController(view!!).navigate(R.id.editAsset, bundle)
}
}
}
}
if i remove this part from the setupViewModel function:
if (viewModel.getAssetResult().hasObservers())
return
the app will either crash when clicked "back" using the device button or go back to fragment A, just to be navigated back to fragment B because of the .observe function.
Override the method onBackPressed() to handle the "<-" arrow
Seems like the LiveData that you use to signal to fragment A that it should navigate to fragment B is actually an event. An event happens only once and once it is consumed (navigation event is done), it is gone. Therefore, after navigating you need to send a message to the viewmodel that the navigation took place and that the corresponding data holder should be (e.g.) null again. In Fragment A you check that the new value is unequal to null, and only if this is the case, you issue the navigation event. This would prevent fragment A to immediatelly switch to B again in the back scenario.
If you want to learn more about ways to use live data for events, please refer to this article.

In Android Navigation Architecture, how can I check if current Fragment is the last one?

I need to display custom AlertDialog, but only when there are no more fragments after calling NavController.navigateUp(). My current code does something similar, but there is a bug to it:
override fun onBackPressed() {
if (navController.navigateUp()) {
return
}
showQuitDialog()
}
This somewhat works, but if I cancel the AlertDialog and don't quit the app, navController already navigated up to NavGraph root, which is not the fragment in which I was when AlertDialog appeared. So, if I try to use any navigation action from that fragment, I get error:
java.lang.IllegalArgumentException: navigation destination
com.example.test/actionNavigateToNextFragment is unknown to this NavController
This could be resolved, if I had access to mBackStack field in NavController, but it's package private, and I can't use reflection in my current project. But if it was public, I would use it the following way:
override fun onBackPressed() {
// 2 because first one is NavGraph, second one is last fragment on the stack
if(navController.mBackStack.size > 2) {
navController.navigateUp()
return
}
showQuitDialog()
}
Is there a way to do this without reflection?
You can compare the ID of the start destination with the ID of the current destination. Something like:
override fun onBackPressed() = when {
navController.graph.startDestination == navController.currentDestination?.id -> showQuitDialog()
else -> super.onBackPressed()
}
Hope it helps.
Try this for any destination (you can find your destination id in the navigation graph):
private fun isDesiredDestination(): Boolean {
return with(navController) {
currentDestination == graph[R.id.yourDestinationId]
}
}
Comparing IDs of destination may not be the best solution because you may have multiple screens with the same ID but different arguments on the backstack.
Here's an alternative:
val isRootScreen = navController.previousBackStackEntry == null
if you want this is a button so to speak you can have this
yourbutton.setOnClickListener {
val currentFragment = navController.currentDestination?.id
when (navController.graph.startDestination == currentFragment) {
true -> { //Go here}
// Go to the app home
else -> onBackPressed()
}
}

Two Fragments overlapping each other

I am using a BottomNavigationView for a Tab-Interface in my main activity. in onCreate(), the switchTab method is called with the initial fragment. Tapping the the bottom navigation calls switchTab() for the respective tabs and should hide the current and display the new one. If the fragment was not added to the SupportFragmentManager, it gets added, otherwise it will be shown. Here's my code snippet:
private fun switchTab(fragment: Fragment, tag: String): Boolean {
val currentFragment = supportFragmentManager.fragments.find { it.tag == tag }
val ta = supportFragmentManager.beginTransaction()
if (currentFragment != null) {
ta.hide(currentFragment)
}
if (supportFragmentManager.fragments.contains(fragment)) {
ta.show(fragment)
} else {
ta.add(R.id.contentContainer, fragment, tag)
}
ta.commit()
return true
}
Now the problem is that sometimes two fragments are visible and overlay each other, making the userinterface unusable. How can this happen?

unsaved warning on back pressed in fragment

I want to show dialog when user press back or quit from fragment if there are some data unsaved. I am trying to override onbackpressed but unfortunately I got error lateinit property barcodeList has not been initialized. how to solve it?
here is my script on activity:
override fun onBackPressed() {
val theFragment = supportFragmentManager.fragments
for(i in 0 until theFragment.size)
{
if(theFragment[i].tag == "stocker_fragment")
{
StockerFragment().onBackPressed()
}
}
}
and this is in fragment:
fun onBackPressed() {
var check = false
// this barcodeList variable error.
for(i in 0 until barcodeList.size)
{
if(barcodeList[i].barcode.trim()=="")
{
check = true
break
}
}
if (check)
{
AlertHelper(context).onBackPressedAlert()
}
}
FYI: I have initialized barcodeList on onCreateView and everything is fine. only error in onBackPressed.
And my last question is, how do i know if user quit from fragment without pressing back button?
I think the problem is in your onBackPressed() implementation in the Activity. With the line StockerFragment().onBackPressed() you are creating a new instance of the StockerFragment and calling onBackPressed() on it, rather than calling it on the instance that is actively being used.
You should be able to adjust your Activity onBackPressed() like so:
override fun onBackPressed() {
val theFragment = supportFragmentManager.fragments
for(i in 0 until theFragment.size)
{
if(theFragment[i].tag == "stocker_fragment")
{
(theFragment[i] as StockerFragment).onBackPressed()
}
}
}
You can also make this a bit more kotliny like so:
override fun onBackPressed() {
supportFragmentManager.fragments.forEach { fragment ->
if (fragment is StockerFragment) {
fragment.onBackPressed()
}
}
}
You'll probably also want to figure out a way to decide whether the fragment's onBackPressed has determined that the Activity should stick around or not. Then, if the fragment is happy, you should call super.onBackPressed() in the Activity so that the expected back behavior (leave the Activity) happens.

Categories

Resources