Android : Viewpager with fragments calling asynctask - android-fragments

i've viewpager which calls fragment (by a fragmentpagermanager) in which I start an asynctask.
In the function
setUserVisibleHint
I'm launching my asynctask, and it's running. The problem is, the view pager is loading several fragments (i've one fragment, I'm calling the same, just datas change) in the same time, and when the asynctask is finished, my listview don't shows data, my data are present in the next fragment... not in the current fragment.
So the problem is, I don't arrive to launch the asynctask for the current fragment, and to show them directly.
It's hard to explain, i hope some understands :D

Related

fragment vs activity onClickButton functions - where is right place

I made an andorid application using the "activity" component. I used it for a while, but over time I found that one page was not enough for me. I needed to add tabLayout, viewPager2 and three fragments.
I needed to migrate the functionality from mainActivity to the firstFragment.
I encountered a problem with events during the XML migration. In the activity I called android: onClick = "myOnClicFunction". But is that not possible?
I know that this has been discussed several times here in several threads. However, I would like to ask how to proceed correctly.
Here in the thread Android Fragment onClick button Method is the advice "even if you are in the fragment, put the onClick functions into activity" there is another similar advice to, "if you insist to make it in a fragment"
But what is right? Should the onclick function be correct in the activity or in the fragment?
I don't want to put the source code here, because I don't know which procedure is correct at all.
I am creating a program that is for testing students. An example is displayed and a response is pending.
The first tab has a lot of buttons + saves the result to an xml files.
The second tab will contain statistics. From saved files displays how many calculations were correct, wrong, average response time, etc ...
In Activity I had functions like getQuestion(), evaluateQuestion(), saveToXml() and events zeroWasPressed(), oneWasPressed(), twoWasPressed(), threeWasPressed() ...
Where should I put all these functions when I change the application from Activity to Fragment?
on click function listener should be placed in corresponding fragment, where the button is added in fragment layout.
events zeroWasPressed(), oneWasPressed(), twoWasPressed(), threeWasPressed() ... should be placed inside corresponding fragment where those buttons were added in layout.
getQuestion(), evaluateQuestion(), saveToXml() , this function can be placed in your activity or some other custom classes, that would appropriate according to architecture viewpoint.

Send Data from Fragment to Main Activity through NavGraph

In my app, I'm using a Single Activity with multiple Fragments Architecture and I navigate between them using the Navigation Library. Within one of the fragments, I have multiple categories, each associated with an ID. When a category is clicked, I take the user to that respective category explainer screen with the code below.
val directions = MainNavGraphDirections.launchFragmentWithThisCategoryId(categoryId!!)
onRoute(AppRoute.Directions(directions))
The above code sends them to the explainer screen associated with the associated categoryId. All is well until this point, the right explainer screen gets launched based on the categoryId. Within this explainer screen, I have a deep-link with a tag chatbot://fragment/wizardintro that is supposed to let the main activity know the specific follow-up fragment to send the user to. I denote all the fragments that can receive this deep-link with the code below.
companion object{
const val DEEP_LINK = "chatbot://fragment/wizardintro"
}
In the MainActivity, I have a method that receives all the different deep linking intents and matches them to the tags that will launch the respective category fragment with the code below.
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.data?.toString().also { deepLink ->
when (deepLink) {
IntroductionFragment.DEEP_LINK ->{
val categoryId = intent?.getLongExtra("categoryId", 0L)
val directions = MainNavGraphDirections.actionGlobalGoalWizard(categoryId)
navController.popBackStack()
navController.navigate(directions)
}
}
Now my problem arises when I try to retrieve this categoryId in the Main Activity and pass it to the next fragment. I don't get anything and only the default Long gets passed along. I think the function override fun onNewIntent(intent: Intent?) { } in the MainActivity recieve any intent. To be clear, these intents are sent from the explainer fragment which is technically a fragment that loads a json. Within the json there is a "route": {"type": "route", "url": "chatbot:///fragment/wizardintro"
In the MainActivity, the onNewIntent functions receive these intents unpacks them with this line intent?.data?.toString().also ...then in the when statement picks a fragment that has a matching chatbot:///fragment/wizardintro
I said all this to say that the main activity doesn't actually gets the categoryId, it simply picks launching the neccessary fragment without actually having anything associated with the categoryId
This makes me think that the first clicked categoryId doesn't actually get passed to the MainActivity. Although, to me, this seems like it shouldn't be this hard to pass objects/data from a fragment to an activity. What am I missing? What can I read to educate myself more on this?
Thanks for your time and responses!
Since we already got to the conclusion that MainActivity is not getting categoryId, you just need to pass that categoryId with the deep-link.
However, there is no need for any communication from Fragment to Activity.
You could achieve the same result through communication only between Fragment to Fragment, and Activity to Fragment.
What you want to do is to look more closely on deep-links and android navigation in the AndroidDocs, click here.
As you can tell, there are different ways to go around this, starting with arguments for each fragment. Assigning categoryId as an argument to the Fragment would help you use the navigationController and navigate to the new Fragment, while passing the categoryId to it.
Now, I am aware that you also wish to launch it with a deep-link; there's also a good explanation on here. According to the docs, you can place arguments in deep-links in the following manner...
Placeholders in the form of {placeholder_name} match one or more characters. For example, http://www.example.com/users/{id} matches http://www.example.com/users/4. The Navigation component attempts to parse the placeholder values into appropriate types by matching placeholder names to the defined arguments that are defined for the deep link destination. If no argument with the same name is defined, a default String type is used for the argument value.
The navigation is something amazing and capable, you just need to be aware of everything it can actually do. You can even bind it to a BottomNavBar, with extremely minimal amount of code.
Try going over the AndroidDocs about it, and it'll grow on you for sure.

Viewpager with fragments screwed up after the system destroyed activity (to recover memory)

I have a viewpager with 3 pages and a fragment (containing inner fragment) on each page. My fragments and the activity communicate directly between each other via saved references (I don’t use interfaces or callback). Also I deactivated rotation, my application is used only in portrait mode so far. I call setOffscreenPageLimit(3) and I never call setRetainInstance().
The application works perfectly when it is launched after being properly destroyed.
But when the application is started after the application was destroyed by the system to recover memory and resources (I do this by pausing my app and launching a few heavy applications), viewpager does not start/attach fragments “normally“ and everything screws up.
I have nullpointerexceptions everywhere. For example, my reference to the activity (saved in onAttach) in a fragment is often null. I used to Runnable to give time but even after 2 seconds, though my traces indicate that all fragment callback functions till onResumed have been called, isAdded() return false and getActivity() return null inside the fragment. Even for the fragment that is supposed to be first displayed. Sometimes some fragments are correctly added.
Of course, the difference between the two scenario above is that in the non-working scenario something happens in onCreate(Bundle) or onRestoreInstanceState(Bundle) . There may be an attempt to reused fragments or whatever. So a workaround is to deactivate the saving of the activity state like that:
#Override
protected void onSaveInstanceState(Bundle outState) {
//super.onSaveInstanceState(outState);
}
This workaround works but I lose the possibility to use onRestoreInstanceState in the future.
If you have run across this issue, what other nicer solutions have you found? Thanks in advance.

Android Fragment state and setRetainInstance

Please excuse the long post. I was playing around with a simple app and wanted to save a custom object in a fragment across an orientation change. Previously within activities this used to be handled using the onRetainNonConfigurationInstance() / getLastNonConfigurationInstance() methods. Seeing as these methods are now deprecated, the documentation encourages the use of fragments and the setRetainInstance(boolean) method.
I went ahead and played around with this method and then noticed a strange difference in behaviour when it came to saving the state of the fragments across orientation change. First up, a very brief explanation of the app I was playing with:
Main Activity
Fragment A (First fragment shown on app launch)
This is a simple fragment with 3 EditText controls. Each one has an ID in the layout file. The fragment also includes a button which when selected replaces Fragment A with Fragment B and saves the transaction on the backstack.
Fragment B
This is a fragment with an empty layout. If back is pushed, Fragment A is restored from the backstack.
Scenarios
Scenario A - setRetainInstance(false):
App launches and fragment A is displayed.
I enter values into the EditText fields and select the button.
Fragment B is displayed. I change device orientation once and hit the back key.
Fragment A is displayed with the entered values (view state) intact.
Scenario A - setRetainInstance(true):
The same behaviour takes place as above
Scenario B - setRetainInstance(false):
App launches and fragment A is displayed.
I enter values into the EditText fields and select the button.
Fragment B is displayed. I change device orientation twice and hit the back key.
Fragment A is still displayed with the entered values (view state) intact.
Scenario B - setRetainInstance(true):
App launches and fragment A is displayed.
I enter values into the EditText fields and select the button.
Fragment B is displayed. I change device orientation twice and hit the back key.
Fragment A is displayed with empty EditText controls, i.e. none of the entered values (view state) still intact.
For some reason the use of setRetainInstance(true) interferes with the view state of fragment A (on the backstack) when the orientation changes more than once.
Possible Explanation
I started getting nervous about the use of setRetainInstance while not having a full understanding of what was going on, so I dug around in the support library source code to try figure it out. At a very high level, I think this may be what is going on with setRetainInstance(true):
Fragment A is displayed, button is pressed and Fragment A is replaced by Fragment B. As part of this process, the FragmentManager (FM) removes Fragment A and fragmentA.mRemoving flag is set to true.
Change orientation the first time. At this point the FM attempts to save all state of the fragments:
Parcelable saveAllState() {
...
if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
fs.mSavedFragmentState = saveFragmentBasicState(f);
Fragment A has a CREATED state and has a null saved state, so it qualifies to have its state saved.
The activity is destroyed as part of the orientation change. Long story short, Fragment A has its state changed to INITIALIZING.
The activity is recreated and an attempt is made to move the state of the fragments to CREATED. However, at this point there is a check in the FM moveToState() method:
if (f.mRemoving && newState > f.mState) {
// While removing a fragment, we can't change it to a higher state.
newState = f.mState;
}
Because fragmentA.mRemoving remains true from step 1 as the fragment was retained (not recreated), it does not have its state increased to CREATED but remains in the INITIALIZING state. Note that even if one presses the back key now, Fragment A will still have its state intact, as a result of its state being saved in step 2.
Change orientation for the 2nd time. Once again the FM attempts to save all state of the fragments:
Parcelable saveAllState() {
...
if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
fs.mSavedFragmentState = saveFragmentBasicState(f);
However, because Fragment A is in the INITIALIZING state it does not qualify to have its state saved. Hence, once orientation completes for the 2nd time, if the back key is pressed the state of Fragment A is no longer intact.
Questions
Is this behaviour expected? Perhaps this relates to the documentation discouraging the use of setRetainInstance and backstack fragments?
How should we deal with view state and the use of setRetainInstance? Perhaps my use case is incorrect, but I would be nervous using the setRetainInstance functionality with this difference in behaviour.
Once again, sorry for the long post. Feedback will be appreciated as always.

Toast from destroyed activity/fragment, getString issue

I have a fragment with UI that is responsible for doing short async api-calls. When an async task completes fragment triggers a Toast message with a result-string.
My issue is that if a user presses Back button without waiting for the result the app crushes due to a onPostExecute method, which in turn triggers Toast + getString(R.string.whatever) in the fragment that is already detached, because the activity is behind the onDestroy state.
I can check whether the fragment is detached or use a ProgressDialog, which is modal but I think it would be nice to show the toasts without blocking or any other quirks. Any suggestions on how to achieve it? Broadcast receiver in the application class == overkill?
Thanks.
UPD logs:
01-24 12:01:29.626: E/AndroidRuntime(616):
java.lang.IllegalStateException: Fragment
HandsetSocialNetworksFragment{40612a78} not attached to Activity 01-24
12:01:29.626: E/AndroidRuntime(616): at
android.support.v4.app.Fragment.getResources(Fragment.java:571) 01-24
12:01:29.626: E/AndroidRuntime(616): at
android.support.v4.app.Fragment.getString(Fragment.java:593)
At the start of the async call, get the activity from the fragment, and get the application context from it. Use that one later to get the string you need.
Alternatively, you can fetch the string you need when you start the async call, instead of when you have to show the toast.

Resources