ViewPager2: Listen for setCurrentItem() completion - android

I recently upgraded my app to use ViewPager2.
When the user taps the finish-early button I call this code.
setCurrentItem(myLastPageIndex, false)
doSomeWork()
The doSomeWork() function enables the circular progress bar on the last page.
The thing is that when I call doSomeWork() the page isn't displayed yet and I get an exception because I'm trying to update a View that doesn't exist yet, createFragment() is called after doSomeWork()
This used to work perfectly with the old ViewPager, is there any way to know when the setCurrentItem(myLastPageIndex, false) transition finishes?
I thought about using OnPageChangeCallback, however, I don't want doSomeWork() to be called when the last page is selected by the user, but only when I change the position page dynamically.

Related

View not updating in OnClickListener

i am trying to make an E-Leaning app.
I have a submit button, that when pressed, sends the user's answer to a server to be evaluated.
During that evaluation, I want to display a loading indicator (I am simply using Android Studio's progress bar).
During the onViewCreated of my fragment, I set the onClickListener as follows:
submit_button.setOnClickListener {
submit_button.isClickable = false
loadingIndicator.visibility = View.VISIBLE
inputFragment.passDataToActivityAndEvaluate()
}
On press, a coroutine is launched that makes an html request. After that, I want to hide the loading spinner before navigating the child fragment
loadingIndicator.visibility = View.INVISIBLE
submit_button.isClickable = true
The idea sounded simple enough, however, I discovered that when trying to make any form of UI change in the OnClickListener, it only gets applied when the evaulation is already done and the app has navigated to the next fragment.

Fragment Recreation causes Observer to trigger onChanged() with Androidx Navigation library

Issue:
While working with Navigation Library, I observed when I navigate back to the previous fragment, it recreates the fragment and thus re-registering all my Observers which triggers OnChanged() again
I have a Snackbar which shows some error messages example if I am looking for no more data present or no Internet connection to the server:
deliveriesListViewModel.isMoreDataPresent.observe(this, Observer {
if (!it) showSnackBar(getString(R.string.no_more_data))
})
Source of above code here
And on navigating back and forth, the SnackBar pops up every time, and also every time I change the orientation or rotate my device.
My architecture has a single Activity with startDestination as my ListFragment in the navigation graph and a DetailFragment as destination. SupportNavigationUp or a simple OnBackPressed on DetailFragment returns me to my ListFragment and then recreates the fragment and thus re-registering all my Observers which triggers OnChanged() again and the SnackBar pops up when noMoreDataPresent LiveData is false
Now I tried the solution from here but unfortunately, it doesn't work
I have also tried to switch my LifecycleOwner to my activity by that also doesn't work.
Tried moving the ViewModelProviders.of to OnCreate and onActivityCreated - doesn't work
Please suggest corrections or any ideas what can be done to prevent SnackBar popping up after navigation and orientation change.
Footnotes
I have gone through these issues:
Multiple LiveData Observers After Popping Fragment
How to avoid fragment recreation when tap back button using navigation architecture actions?
Is there a way to keep fragment alive when using BottomNavigationView with new NavController?
here is my complete source code
This article, especially item 1, might be relevant to what you're experiencing. Basically, what happens is that you may have multiple spawned Observers every time you navigate back to your fragment, thus executing onChanged multiple times. Using the fragment's view lifecycle as the LifecycleOwner should prevent this from happening, so your code above would look like this:
deliveriesListViewModel.isMoreDataPresent.observe(viewLifecycleOwner, Observer {
if (!it) showSnackBar(getString(R.string.no_more_data))
})

Fragment isVisible() returns false despite fragment being in foreground

I have a SearchView in the Toolbar of an Activity, which hosts a Fragment which has a ViewPager of 4 tabs, each tab consisting of another Fragment (there is a reason for this structure so I don't want to dwell on it unless someone thinks this is the reason for my problem). When the user originally searches something, everything works fine. However, when the user goes and edits the search query, and hits enter, the tab that was already selected doesn't refresh (it will refresh when we navigate away from it to other tabs and then come back).
The following are screenshots of what I mean (the first image is after the original search where everything is all and well, the second image is me editing the query, the third image is after I hit enter - the results stay the same and nothing is updated since isVisible() in the code below returns false when the fragment to me seems clearly visible).
When a query is submitted essentially what happens is the activity sends the search query to the fragment that contains the ViewPager and TabLayout, which passes it to the current Tab. This is the code that is called in the Fragment that contains the ViewPager after the query is submitted:
public void setSearchQuery (String query) {
//mTabLayout.requestFocus();
mSearchQuery = query;
Fragment fragment = mPagerAdapter.getItem(mViewPager.getCurrentItem()); // should theoretically get the current fragment
mTabLayout.getTabAt(mViewPager.getCurrentItem()).select();
if (fragment != null && fragment.isVisible()) {
((ResultsPagerAdapter.QuerySubmitCallback) fragment).submitQuery(query);
}
}
The submitQuery() line at the bottom is never called because isVisible returns false. However, when logging the lifecycle methods of the current tab the last calls are onStart() followed by onResume(). When I click on the toolbar and edit the query and hit enter, this doesn't change, and the current tab is visible, so I have no idea why this returns false.
I should add in the hosting Activity after the user submits their query, I remove focus from the Toolbar and SearchView so that the keyboard collapses after the search is entered.
When you switch the tabs,the fragment will call setUserVisibleHint(boolean isVisibleToUser).And then,you can get the value of isVisibleToUser by getUserVisibleHint().
If you want to get isVisible of all kinds of fragment,you should update the value of isVisible in such methods:
onAttach and onDetach()
onStart() and onStop()
onHiddenChanged
setUserVisibleHint
onViewAttachedToWindow and onViewDetachedFromWindow

Fragment already added with SwipeRefreshLayout

This is an odd error which I'm battling with at the moment.
I have an activity that displays a search fragment once it has completed initialisation. The search fragment contains a listview to display the results and that listview is inside a SwipeRefreshLayout so that the user can refresh the search results.
If the user selects an item from the list the search fragment is removed and the parent activity is displayed (it has other fragments). The user can choose to open the search fragment and refresh the results if they wish.
The behaviour I have is that if they use the swipe refresh when the fragment is first opened it works as expected. However, if they dismiss the search fragment and then open it again and then swipe down for refresh I get the java.lang.IllegalStateException: Fragment already added exception thrown for the search fragment.
The code to introduce the search fragment the first time is:
#Override
protected void onPostExecute(Void empty) {
dismissSearchProgress();
getFragmentManager().beginTransaction().
setCustomAnimations(R.animator.slide_in_left, 0, 0, R.animator.slide_out_left).
add(R.id.main_vwContent, mWoSearchFragment).commit();
}
The second time the code is introduced via a swipe action on the screen:
case MotionEvent.ACTION_UP:
if (swipeInRange) {
/* do some other stuff */
getFragmentManager().beginTransaction().setCustomAnimations(R.animator.slide_in_left, 0, 0, R.animator.slide_out_left).add(R.id.main_vwContent, mWoSearchFragment).commit();
}
break;
There are no errors thrown in the second instance unless they swipe down to refresh. The error is thrown before the onRefresh event fires.
Anyone have any ideas? Not sure what code is attempting to add the search fragment again as the exception does not have any of my code in the stack trace and the debugger isn't catching anything.
It's amazing what writing up a problem will do for you. The issue was that to open the fragment the user has to swipe from a particular screen location. So on MotionEvent.ACTION_DOWN we check to see if they have started within the target area and set a flag. Then on 'MotionEvent.ACTION_UP' the fragment is added. However, in the case of a SwipeRefreshLayout it intercepts the 'MotionEvent.ACTION_DOWN' event however it pushes the 'MotionEvent.ACTION_UP' up the stack.
So in my case, the flag was still set to true as the last action taken before the refresh was to swipe to add the fragment.
The fix was to ensure that the flag was reset to false when the search fragment was added.

xamarin android viewpager fragments not getting refreshed

I'm using this sample to create a simple app, it's made by Joshep Raco (JoeRock11)
https://github.com/JoeRock11/Xamarin_DesignLibrary
I have a problem, I was working with this sample to create a tabbed app, and I found an strange behavior, when the app is starting only the fragment1 and fragment2 loads , but not the fragment3 (when I say load I mean their oncreate and oncreateview method gets triggered ).
To load the fragment3 I have to explicity click it, I thought all the fragments would get load but is not the case, don't know why, I would like to know why this happens.
Also, and this is my main problem and I dont know how to fix it, every time I click a fragment tab, I want this fragment to reaload, now seems like it just shows the instantiated fragment on memory, because it doens't triger any method of the fragment, I need to load it again, because I need to refresh its data, now it only reloads randomly. can you or anyone help me solve this please?
Thanks a lot in advance.
pd: sorry for my english, it's not my mother language.
I believe this is controlled by the Off Screen Page Limit on the ViewPager control. This value defaults to 1, which basically means, the number of the pages the ViewPager should load at a time is 2, the page that is currently displayed, and the one off screen.
This value can be easily adjusted using the OffscreenPageLimit property on the ViewPager.
http://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int)
If you want all Fragments to reload data once they are slid on to the screen, you can override UserVisibleHint on each Fragmentand then check to see if the Fragment is visible and execute some code to reload data for that fragment:
public override bool UserVisibleHint {
get {
return base.UserVisibleHint;
}
set {
base.UserVisibleHint = value;
if (value) {
//Fragment is visible, reload data
}
}
}
http://developer.android.com/reference/android/support/v4/app/Fragment.html#setUserVisibleHint(boolean)
Another approach to updating the ViewPager fragments is outlined here: Update ViewPager dynamically?

Categories

Resources