In my Android app, I have found an error that happens in about 0.5% of attempts. This error is generate from an NullPointerException but this is strange.
Below is summarized the code the generates that exception:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
mInfo = intent.getParcelableExtra(EXTRA_MESSAGE_INFO); // mInfo is a private member of Activity
if(mInfo == null)
finish();
setContentView(R.layout.activity_fermata_details);
first = savedInstanceState.getBoolean(IS_FIRST_LOAD, true); // also onSaveInstanceState is used for maintain that value across configuration changes.
if(first){
first = false;
// a new fragment is inflated in the FrameLayout of the Activity
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
DetailsFragment fragment = new DetailsFragment();
transaction.replace(R.id.content_fragment, fragment);
transaction.commit();
}
}
The Activity also has the following public method:
public int getIdInfo(){
return mInfo.getId(); // here occurs the NullPointerException.
}
The method getIdInfo() is called from the Fragment in this way:
(MyActivity)getActivity()).getIdInfo();
I think that is a strange behavious, since the NullPointerException tells that mInfo should be null. But the condition in the onCreate:
if(mInfo == null)
finish();
should ensure that if the mInfo is null the Activity is finished.
Where can be the error? Can be related to Garbage Collector behaviour?
NOTE: I'm not able to get much more infomation about the error (the error was detected 3 times over 600 attempts), since I have retrieved this exception form Google Analytics, that gives my just this row:
NullPointerException (#MyActivity:getIdInfo:122) {main}
I think you are getting not-fully created Activity: one that has detected null and called finish(). Please consider the fact that finish() does not cause the onCreate() method to stop.
I suggest:
mInfo = intent.getParcelableExtra(EXTRA_MESSAGE_INFO);
if(mInfo == null) {
finish();
return;
}
One could suspect that FragmentManager will not try to attach any fragments to activity that called finish() on itself but I'm not able to find any documentation of that behavior.
Related
So im having some trouble restoring the state of my Activity.
At this point I figure that its probably a problem with my understanding rather then anything else.
My goal is to move from my MainActivity to a Main2Activity.
Am I correct in thinking that when a user moves from one page to another, it should be done by changing Activity via Intent?
I am doing this like so:
The onCreate() for my MainActivity has this in it.
Button currentButton = (Button) findViewById(R.id.button2);
currentButton.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View view) {
setContentView(R.layout.activity_main2);
Intent nextIntent = new Intent(getApplicationContext(), Main2Activity.class);
startActivity(nextIntent);
}
}
);
Which as I understand should call onCreate(), onStart() and onResume() for Main2Activity, then onSaveInstanceState() for MainActivity, then onStop() for MainActivity.
Ive overloaded all those functions with logging and seen that indeed they are being called and in that order.
Here is my onSaveInstanceState() for MainActivity:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
System.out.println("onSaveInstanceState called, saving state");
savedInstanceState.putInt("mySuperUniqueKey", testInt);
super.onSaveInstanceState(savedInstanceState);
}
Once in Main2Activity, I return back to MainActivity in the same way. I.e. findViewById() the button, overload its onClickListener(), create a new Intent and start it.
Then MainActivity class's onCreate() has this :
if (savedInstanceState != null) {
System.out.println("savedInstanceState is not null");
testInt = savedInstanceState.getInt("mySuperUniqueKey");
} else {
System.out.println("savedInstanceState is null");
}
When returning back the MainActivity from Main2Activity, I can see from the logging that onCreate(), then onStart(), then onResume() for MainActivity is called, then onStop() for Main2Activity. Unfortunatly the logging shows that savedInstanceState always comes back as null.
To add to this, when in the emulator, switching the orientation back and forth causes this to work perfectly; savedInstanceState is not null and features the saved testInt.
Thus I figure its a problem with my understanding and that there must be something im missing.
My gradle has minSdkVersion set to 16, and targetSdkVersion set to 28. Am I maybe targeting too low a minSdkVersion?
I have read through the "Understand the Activity Lifecycle" on the official android developer documentation but still cant get it.
https://developer.android.com/guide/components/activities/activity-lifecycle
I did find similar problems but none of them match my situation exaclty, also the solutions they have suggested I am already doing anyway.
Any insight would be greatly appreciated, thanks in advance.
The saved instance state bundle is intended to save the state of the current activity across things like orientation changes. It is not designed to persist across activities. You should use Intent#putExtra:
nextIntent.putExtra("mySuperUniqueKey", testInt);
Then, in your next activity, access this passed value using:
int testInt = getIntent().getIntExtra("mySuperUniqueKey");
I am updating the view that is currently showing and I Hide/Show two fragments.
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (mTaxLocationListViewFrag.isVisible()) {
if (mGoogleMapFrag == null) {
mGoogleMapFrag = new GoogleMapFragment();
fragmentTransaction.add(R.id.fragment_container, mGoogleMapFrag);
}
fragmentTransaction.hide(mListViewFrag);
fragmentTransaction.show(mGoogleMapFrag).commit();
} else {fragmentTransaction.hide(mGoogleMapFrag).show(mListViewFrag).commit();
}
updateDisplay();
updateDisplay method-
if (mListViewFrag != null && mListViewFrag.isVisible()) {
Log.d(TAG, "Update Location ListView");
mListViewFrag.setLocations(mLocations);
}
if (mGoogleMapFrag != null && mGoogleMapFrag.isVisible()) {
Log.d(TAG, "Update Google Maps Markers");
//Convert locations to markers to display.
}
The problem is I am guessing fragmentTransaction is asynchronous when performing .show and .hide and updateDisplay does not work properly as the it thinks the fragment to be hidden is still showing. Is there a more reliable way to check for which fragment to update?
Add fragmentTransaction.executePendingTransactions() after adding the fragment. From the docs of executePendingTransactions():
It is scheduled to be executed asynchronously on the process's main thread.
If you want to immediately executing any such pending operations, you
can call this function (only from the main thread) to do so. Note that
all callbacks and other related behavior will be done from within this
call, so be careful about where this is called from.
I have main activity and 2 fragments on it, fragment1 call fragment2
When I am clicking the back button from the built in android buttons it call the onBackPressed function that I overrided in the main activity and exit me from the app.
I want that when I click back on the fragment 2 the onBackPressed of the main activity will not called and it will back me to fragment 1.
The back button of the action bar works well (I think it mean that the backstack is ok).
I tried to do:
getActivity().getSupportFragmentManager().beginTransaction().addToBackStack("Messages");
on the fragment 2
And tried to add
getSupportFragmentManager().popBackStack();
to the onBackPressed() of the main activity
It dosen't did nothing
Thank you
EDIT:
I am trying to do
public void onBackPressed() {
// getSupportFragmentManager().popBackStack();
android.support.v4.app.Fragment fragment2 = getSupportFragmentManager().findFragmentByTag(null);
if (fragment2 != null && fragment2.isVisible()) {
getSupportFragmentManager().popBackStack();
return;
}
and before any show
getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();
tr.show(Fragment2);
but got null on the findFragment(null)
EDIT:
I checked getSupportFragmentManager().beginTransaction().mTail.fragment and saw that the value is the Fragment 1 that I want to be showed, but when I do getSupportFragmentManager().popBackStack(); it dosen't nothing.
Override onBackPressed() in Main activity.
Check the currently displayed fragment, if fragment2 is displayed, then pop back stack.
example:
#Override
public void onBackPressed() {
Fragment fragment2 = getFragmentManager().findFragmentByTag("tag");
if (fragment2 != null && fragment2.isVisible()) {
getFragmentManager().popBackStack();
} else {
finish();
}
}
just use below way; in your Activity check this
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
Fragment fragment = getSupportFragmentManager().findFragmentByTag("SecondFragment");
if (fragment instanceof SecondFragment) {
// show first fragment
}else{
// finish();
}
}
i have used findFragmentByTag, you can also use findFragmentById
i agree with #Minhtdh comment but it only applicable if you replace one over other
Before calling fragment2.show() in the MainActivity, add the fragmnet transaction to backstack. There is no need to override onBackPressed().By default android implements the same functionality for you.
simple working code.
to get back to 1st fargment.
yourviewvariable.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getActivity().onBackPressed();
}
});
Sorry that I miss typed, I mean method onBackPressed.
But for your case, because you use FragmentTransition.show(), it will not add the fragment to backstack and popBackStack won't work. So I think you should try this:
1. don't called getSupportFragmentManager().beginTransaction().addToBackStack(null).commit(); because it add an unnecessary backstack.
2. don't store the "tr" variable as global variable, and you also need call commit() to end the transaction:
Fragment2.setTag("fragment2");
getSupportFragmentManager().beginTransaction().show(Fragment2).commit();
3. in onBackPressed():
android.support.v4.app.Fragment fragment2 = getSupportFragmentManager().findFragmentByTag("fragment2");
if (fragment2 != null && fragment2.isVisible()) {
getSupportFragmentManager().beginTransaction().hide(fragment2).commit();
return;
} else {
super.onBackPressed();
}
Hope this help.
I already checked a lot of same questions on StackOverflow, but I didn't find any solution to my issue.
In a DialogFragment, I call an AsyncTask method and when the result has been received from the server, I launched another DialogFragment.
Here is the code I use to launch the DialogFragment :
public class RequesterConfirmRent extends DialogFragment {
// Called from onPostExecute() in AsyncTask class.
public void onPostComputeAmountToPay(JSONArray array){
double amountToPay = 0.0;
String ownerName = "";
try{
if(!array.getJSONObject(0).getBoolean("success"))
Log.e("Error", "Error with JSON received");
else {
amountToPay = array.getJSONObject(1).getDouble("amountToPay");
ownerName = array.getJSONObject(2).getString("ownerName");
}
}catch(JSONException e){
Log.e(e.getClass().getName(), "JSONException", e);
}
// Create and show the DialogFragment
Paiement p = new Paiement();
Bundle bdl = new Bundle();
bdl.putString("ownerName", ownerName);
bdl.putDouble("amountToPay", amountToPay);
p.setArguments(bdl);
// Buggy line (NPE)
p.show(getFragmentManager(), "4554");
}
}
And here is the code of the DialogFragment I try to display:
public class Paiement extends DialogFragment {
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if(getDialog() == null)
super.setShowsDialog(false);
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.setTitle("Synthesis of your rent");
return dialog;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_paiement, container, false);
init(rootView);
return rootView;
}
private void init(View v){// Bla bla ...}
}
And I alway got a NullPointerException when I call the .show() method?
What did I do wrong?
Many thanks for your help!
EDIT 1 : As requested, here is the LogCat
05-11 09:58:34.470 31384-31384/com.example.celien.drivemycar
E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NullPointerException
at android.support.v4.app.DialogFragment.show(DialogFragment.java:136)
at com.example.celien.drivemycar.fragment.RequesterConfirmRent.onPostComputeAmountToPay(RequesterConfirmRent.java:148)
EDIT 2 I modified the code like this, and it appears that getFragmentManager() is null. Why?
Paiement p = new Paiement();
Bundle bdl = new Bundle();
bdl.putString("ownerName", ownerName);
bdl.putDouble("amountToPay", amountToPay);
p.setArguments(bdl);
// BUGGY LINE
android.support.v4.app.FragmentManager f = getFragmentManager();
if(p == null)
Log.d("Exception ", "p is null");
if(f == null)
Log.d("Exception ", "f is null");
try {
p.show(f, "4554");
}catch(NullPointerException e){
Log.d("Exception ", e.toString());
}
EDIT 3:
Got some fresh infos! To avoid the creation of this Dialog, I display data in a Toast: Toast.makeText(this.getActivity(), "You have to pay "+amountToPay+"e to " +ownerName, Toast.LENGTH_LONG).show(); and also get a NPE!
BUT if I use the Log system, everything's fine :
Log.d("Rcvd ", String.valueOf(amountToPay));
Log.d("Rcvd ", ownerName);
So, why is my activity null?
If you get a NPE when calling p.show() but not p.setArguments(), it could be that p is ok but something inside the show call isn't?
On possible point to solve is that you're using a support version of FragmentManager, with the getFragmentManager() call. Try the getSupportFragmentManager() instead. It will fall back to the proper one when needed.
On the other hand, you're calling android.support.v4.app.FragmentManager. If you manually added the package, it's weird, so chances are your IDE did it for you. You could try to remove the package behind FragmentManager, and hope for the code be compliant to the non-support standard framework. Could be that only this reference to the support library is done, so removing the package part would solve the issue.
My advice: In an app, always stick to either the standard framework or the support library when defining activities and fragments. Because of that, make sure that every Activity and Fragment you create extends a proper support (or standard framework) version. Mixing them will end up with unexpected crashes.
Also, as mentioned in one of my comments, AsyncTask runs freely even after your fragment was detached, so no activity is properly referenced by this fragment anymore. This answer tells you to check if Fragment was detached by looking at isDetached(). Check for his answer. He's talking about using Loaders instead of AsyncTasks or move the AsyncTask up to the activity, so the activity is always available. Looking at the future, Loader is the best option (since it's the natural evolution of AsyncTask), looking at the present, try to move the AsyncTask up to the common Activity.
I have fragments created that depends on few properties of its parent activity. I went through the Fragment Life cycle documents. I need to store the copy of activity in a variable so that I can access it later. There are two place, I could do this
onAttach()
onActivityCreated
Which is the best place that would be recommended and why? There has been instances that getActivity return null in the fragment after onAttach() is called
If getActivity() returned null after onAttach() was called, it would only have meant that the fragment was no longer attached to activity. So the safest place is still onAttach().
In recent days, I am researching the fragment. the fragment life cycle is described as following:
onAttach()--onCreate()---onCreateView()--onViewCreate()--onActiviyCreate();
then I look up the source code:
#CallSuper
public void onAttach(Context context) {
mCalled = true;
final Activity hostActivity = mHost == null ? null : mHost.getActivity();
if (hostActivity != null) {
mCalled = false;
onAttach(hostActivity);
}
}
#Deprecated
#CallSuper
public void onAttach(Activity activity) {
mCalled = true;
}
we found the onAttach() checks if the hostActivity has existed, but it can not make sure the activity exists. onActivityCreate() is called when fragment's activity has been created, and tells the fragment that is fully associated with the new activity instance. in other words, activity has completed its own Activity.onCreate().
so I suggest you do it on onActivityCreate() method.