After I receive a response with Volley, I have to get back to the main fragment.
I have two different volley requests , depending on some condition, I'll call it 'a' in this example.
The weird thing is the when a==1, popBackStack gets me successfully to the main fragment .
When a==0 it crashes and I receive java.lang.IllegalStateException Fragment not attached to Activity
I tried creating a new main fragment (transaction.commit....) but it didn't help.
if( a == 0 )
{
VolleyManager.add(jsnObj,
new RequestListener() {
#Override
public <T> void onSuccess(T object) {
mFragmentManager.popBackStack(DataManager.BACK_STACK_KEY_MAIN_FRAGMENT, 0);
}
});
}
else if( a==1 )
{
VolleyManager.update(jsnObj,
new RequestListener() {
#Override
public <T> void onSuccess(T object) {
mFragmentManager.popBackStack(DataManager.BACK_STACK_KEY_MAIN_FRAGMENT, 0);
}
});
}
Error -
java.lang.IllegalStateException: Fragment MainFragment{6aaaf7f} not attached to Activity
at android.app.Fragment.getResources(Fragment.java
The problem seems to be with the getResources(), but I do the same thing when a==1 and I've got no problems at all.
It seems like that by the time AsyncTask finishes and calls onPostExecute, the MainFragment has been detached from its activity. So either the activity has already been destroyed or fragment was never attached.
So if fragment is not attached to the activity, it can't access resources because that requires context and fragment doesn't have but activity does.
So you should check if activity is null or not before calling getResources.
Update the code like this:
if(getActivity()!=null){
String streetFormat = getActivity().getResources().getString( R.string.address_name_string );
....
}
You have to cancel your requests on
onDestroyView()
method of the fragment or check if the fragment is already alive and added to host activity or not
I'd go with something like this:
onDestroyView(){ Volley.cancelAllRequests() }
or
onResponse(){ if(getActivity() != null && isAdded(){ // here handle the response and update views, otherwise just cache the response!}}
getResources() must be called from something that has Context, like the activity. The fragment itself does not have the Context since it does not implement it. If you're using getResources() in a fragment, you can try this:
String streetFormat = getActivity().getResources().getString( R.string.address_name_string );
Related
My project uses Firestore as the database. I am having a fragment which when opened requests a document from Firestore in its onCreate method. I have attached an OnCompleteListener to the .get() method so that when the document fetch is complete, it updates UI.
The issue is that sometimes the user, after opening the fragment, quickly moves on to another fragment before the onCompleteListener is triggered. Or in some cases, the same fragment is called twice and while the first instance of the fragment is destroyed, its onCompleteListener is still alive and triggers after the first instance of the Fragment is destroyed. In both these scenarios, I get an exception
Fatal Exception: java.lang.IllegalStateException: Fragment ProfileFragment{4ee762 (a8f7ae01-23be-4d47-b695-68e273b992bf)} not attached to a context.
at androidx.fragment.app.Fragment.requireContext(Fragment.java:774)
at androidx.fragment.app.Fragment.getResources(Fragment.java:838)
at androidx.fragment.app.Fragment.getString(Fragment.java:860)
at com.desivideshi.productinfo.ProfileFragment$18.onComplete(ProfileFragment.java:903)
at com.google.android.gms.tasks.zzj.run(com.google.android.gms:play-services-tasks##17.1.0:4)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at com.google.android.gms.internal.tasks.zzb.dispatchMessage(com.google.android.gms:play-services-tasks##17.1.0:6)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6898)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Here is my code snippet inside ProfileFragment's onCreate method
documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (mContext != null){
if (task.isSuccessful() && task.getResult() != null && task.getResult().getData() != null) {
Map<String, Object> productsDoc = task.getResult().getData();
productsList = (List<Map<String, String>>) productsDoc.get(getString(R.string.fs_f_products_list));
if(productsList == null){
productsList = new ArrayList<>();
}
mMyProfileData.setMyProductsList(productsList);
}
} else {
Toast.makeText(mContext, "Failed to load products", Toast.LENGTH_SHORT).show();
}
createTabLayout();
profileTabLayout.selectTab(profileTabLayout.getTabAt(profileTabLayout.getSelectedTabPosition()));
progressLayoutProfileTab.setVisibility(View.GONE);
}
}
});
Is there a way to remove the callback to OnCompleteListener which I can place in the onDestry() or onDetach() method of the Fragment in order to avoid this exception?
How to remove OnCompleteListener on .get() method when fragment is destroyed?
There is no need to remove any listeners, because the get() method gets the data exactly once and that's the end of it. If you have been used addSnapshotListener(), you should have removed the listener according to the life-cycle of your activity. In your case, the onComplete() method will fire only when the data is completely loaded from the database. What you need to know is that the Cloud Firestore client runs all network operations in a background thread. So don't do things that related to a context in a background thread.
If you need a real-time listener, then use the addSnapshotListener() and stop listening for changes once your activity/fragment is stoped. Please also remember that onDestroy is not always called, so stop listening for changes in onStop().
Edit:
Because you are using things that are related to a context inside the callback, you should query the database only is isAdded() method returns true.
If you are going to make changes to views directly in your listener (which is not really a good idea, more on that later), then you use should an activity-scoped listener on the Task object returned by get(), so that the listener won't trigger after the activity becomes stopped.
documentReference.get().addOnCompleteListener(activity, callback)
Note that you're passing the activity instance as the first argument, before the callback.
However, it would be better for modern Android applications to use MVVM architecture, as recommended by Google throughout Android documentation, to abstract away the database code from the views by exposing only a LiveData, which is aware of the activity lifecycle and will not emit events to observers after the activity stops. But that's entirely up to you.
The most easy way I think could be use a try catch so when you get the exception, nothing happens, it's a controlated exception.
The best way of solve this problems is to use a MVVM pattern with LiveData. ViewModel is aware of the lifeCycle of the activity / fragment and when your fragment dies, you don't get any background notifications from calls you did when the fragment was alive.
Try this
documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
try{
if (mContext != null){
if (task.isSuccessful() && task.getResult() != null && task.getResult().getData() != null) {
Map<String, Object> productsDoc = task.getResult().getData();
productsList = (List<Map<String, String>>) productsDoc.get(getString(R.string.fs_f_products_list));
if(productsList == null){
productsList = new ArrayList<>();
}
mMyProfileData.setMyProductsList(productsList);
}
} else {
Toast.makeText(mContext, "Failed to load products", Toast.LENGTH_SHORT).show();
}
createTabLayout();
profileTabLayout.selectTab(profileTabLayout.getTabAt(profileTabLayout.getSelectedTabPosition()));
progressLayoutProfileTab.setVisibility(View.GONE);
}
}catch(Exception e){
//Use the exception inside a log, let it blank or as you prefer
}
}
});
I am working on an app where I am drawing my custom lockscreen (An Activity) over the system's default lock.
Everything is working perfectly fine except one thing, I am using a reciever and whenever there is an incoming call, that reciever gets called and from inside that reciever I am closing the activity.
Note : This is happening only in case of OnePlus device, on any other device it's working perfectly.
private class CallStateListener extends PhoneStateListener {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
OverlayActivity overlayActivity = new OverlayActivity();
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
// System.out.println("RINGING");
overlayActivity.finish();
// System.out.println("Activity has been closed!!");
break;
}
}
}
Both the lines before and after the code where I am closing the activity is working completely fine, but the activity is not getting closed.
And if you are creating Activity Object its not working at all you
needs and Actual Activity Object or Context of Activity to close it.
I have this piece of code that runs alright when I put it in Eclipse, but for some reason it does not want to execute when I put it in an activity's onCreate method in Android studio.
Here is the code:
public class ItemListView extends AppCompatActivity{
String itemURL;
int listSize;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_list_view);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Bundle itemData = getIntent().getExtras();
if(itemData==null){
return;
}
//Gets URL from search bar
itemURL = itemData.getString("itemURL");
try {
Document doc = Jsoup.connect("https://www.amazon.com/s/ref=nb_sb_ss_c_1_6?url=search-alias%3Daps&field-keywords=rx+390&sprefix=rx+390%2Caps%2C166&crid=2MTUBA4KGNY06").get();
String link = doc.select("h2#s-result-count").first().text();
System.out.println(link);
System.out.println(link.substring(1));
if (link.substring(1, 2).equals("-")) {
System.out.println("run1");
listSize = Integer.parseInt(link.substring(2, 3));
System.out.println(listSize);
try {
listSize = Integer.parseInt(link.substring(2, 4));
System.out.println(listSize);
} catch (Exception e) {
}
} else {
System.out.println("run2");
listSize = Integer.parseInt(link.substring(0, 1));
System.out.println(listSize);
try {
listSize = Integer.parseInt(link.substring(0, 2));
System.out.println(listSize);
} catch (Exception e) {
}
}
} catch (Exception e) {
}
System.out.println("listSize: " +listSize);
...
}
}
I need listSize to create a variable array depending on the URL, but when I print the value to make sure it's working it always gives me 0. I have tried running the code in a separate Java Class with AsyncTask and it works, but by the time the code executes in onPostExecute, it's too late since the class above has already tried to initialize the array.
Any advice would be appreciated, thanks!
What you need is a callback to allow you to initialize variable onPostExecute:
interface OnCallCompleteCallBack {
void onCallComplete(int listSize);
}
In your AsyncTask do this:
public class MyTask extends AsyncTask < ... > {
// Maintain a ref to callback
OnCallCompleteCallBack callback;
MyTask(OnCallCompleteCallBack callback) {
this.callback = callback;
}
...
#Override
protected void onPostExecute(Void aVoid) {
if (callback != null) {
callback.onCallComplete(listSize);
}
}
}
Make your Activity implement OnCallCompleteCallBack and start the AsyncTask like this:
new MyTask(this).execute();
You can then use the value inside your activity's implementation of onCallComplete()
Before I answer, just a few observations:
Naming your activity ItemListView seems wrong (an Activity is not a View).
You should never perform networks calls on the UI thread.
If you want to log, you should use Log instead of System.out.println().
And now to the answer. You should execute the code that fetches the data in an AsyncTask (as you mentions it works). Once the data is fetched, you should update array and at that point update the UI in onPostExecute().
Android works pretty well using the MVC (Model-View–Controller) pattern and your problem is a classic case where you need to update the model using data from the server and update the views accordingly. The activity represents controller in this case.
Please go through the topic in android developer site Android Developer, Read the section "Introducing Network Operations on a Separate Thread" - To avoid creating an unresponsive UI, don't perform network operations on the UI thread. By default, Android 3.0 (API level 11) and higher requires you to perform network operations on a thread other than the main UI thread; if you don't, a NetworkOnMainThreadException is thrown.
Thanks Ashish
In my application, I have to send a list of accounts from one fragment to another, when the Activity starts. I get the list of accounts in the following thread, saving it in a global ArrayList. If the request to the server is good, the ArrayList gets populated with the necessary information. Then, i call the loadAccounts method before transferring data via bundle from one fragment to another. The problem is that the thread doesn't get to finish it's execution before I want to send the data between fragments, hence the ArrayList will be NULL when the data is sent.
How can I make the application wait until the thread executes, and only after that to send the data to the other fragment ?
My thread looks like this:
public void loadAccounts() {
LoadAccounts loadAccountsThread = new LoadAccounts(new Handler() {
public void handleResult(Result result) {
switch (result) {
case SUCCESSFUL_CODE:
accountsList = (ArrayList<Account>) accounts;
break;
case FAILED_CODE:
errorMsg = error.toString();
showDialog(errorMsg);
default:
break;
}
}
});
loadAccountsThread.start();
}
while in the onCreate method I do this:
loadAccounts();
Bundle args = new Bundle();
AccountsFragment fragment = new AccountsFragment ();
args.putSerializable("accounts", accountsList.get(0));
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(R.id.rightConent, fragment).commit();
Any help would be appreciated. Thanks.
You do not want to make your application wait because it will make your app slow or even stuck in case of an error.
Do not send your accounts through the bundle. Instead create a method inside your AccountsFragment
public void setAccounts(ArrayList<Account> accounts){
//do whatever you need with your accounts here
}
and then inside your handleResult method when you have a SUCCESSFUL_CODE, run
fragment.setAccounts((ArrayList<Account>) accounts);
of course to do this, make sure your AccountFragment fragment is a field and not a local variable inside your onCreate. Make sure also that you instantiate your Fragment before running your thread
If you're calling web service use AsyncTaskinstead of thread and put this inside a method
Bundle args = new Bundle();
AccountsFragment fragment = new AccountsFragment ();
args.putSerializable("accounts", accountsList.get(0));
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(R.id.rightConent, fragment).commit();
and call the method from onPostExecute() method using context
I'd like to start a WebView from my AsyncTask but it doesn't seem to run. This is what my onPostExecute method looks like:
public class Viewer extends AsyncTask<URI, Integer, URI> {
private Activity objContext = null;
public Viewer(Activity objContext) {
this.objContext = objContext;
}
protected void onPostExecute(URI uriWebpage) {
WebView wbvBrowser = new WebView(this.objContext);
wbvBrowser.getSettings().setBuiltInZoomControls(true);
wbvBrowser.getSettings().setJavaScriptEnabled(true);
wbvBrowser.loadUrl(uriWebpage.toString());
}
}
My task is used by two activities in my application and therefore my global objContext variable is of type Activity. If I change the type of my objContext variable to the name of the calling class, it works fine but then I can't instantiate my task from the other calling class. I instantiate my task like this.
Viewer mytask = new Viewer(this);
How can I solve this?
Did you setContentView from original layout to webView or you have a container to put webview? otherwise I don't think webView UI appears.
Are you sure you execute your AsyncTask
#Override
protected void onPostExecute(Void result){
}
}.execute();
Also need to class name
Viewer mytask = new Viewer(YourclassName.this);
// or This may Helps you
private Context mcontext;
public Viewer(Context objContext) {
this.mcontext= objContext;
}
protected void onPostExecute(URI uriWebpage) {
WebView wbvBrowser = new WebView(((Activity)(mcontext)));
}
I have solved the issue. It has nothing to do with the use of Activity or Context. Both work just fine. Some URLs don't seem to load the WebView. I changed the URL of the WebView to point to a site like Google and it worked just fine. Seems like if the URL is incorrect, the WebView doesn't throw an exception but doesn't open up either.
I don't know why this happens, but why don't you just start the built in web browser through an Intent in your onPostExecute(Uri uriWebpage)?
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uriWebPage);
startActivity(intent);
If this solution doesn't satisfy you, then please post the error stack trace from LogCat.