Handle Permissions Request Result Without Subclassing Activity and Overriding onRequestPermissionsResult Method - java

I am calling a permissions request from a non-Activity based class inside of a Cordova plugin, and the main activity of my application cannot be extended so I cannot implement/override the onRequestPermissionsResult method of the activity. I need to run an event after the user has authorized permissions like the commented pseudocode lambda below, but I am not sure how. I have tried creating my own Activity subclass and implementing this method, but I get a NullPointerException.
class MyPlugin extends CordovaPlugin {
private void saveAndOpenFile() {
int permission =
ActivityCompat.checkSelfPermission(
cordova.getActivity(),
Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
cordova.getActivity(),
PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE
); /*.then((int requestCode, String[] permissions, int[] grantedResults) -> {
// Do something crazy
});*/
}
}
}

I suggest following the documentation on this one.
In short, it appears you should use cordova.hasPermission to check if we have the permission, then use cordova.requestPermission, which "will call the activity and cause a prompt to appear, asking for the permission. Once the user has the permission, the result must be handled with the onRequestPermissionResult method, which every plugin should override." So, it sounds like CordovaPlugin offers an empty base implementation of onRequestPermissionResult that your plugin should override.
Granted, I imagine you can probably get away with calling ActivityCompat.checkSelfPermission and Acitivity.requestPermissions, but either way, your plugin should override onRequestPermissionResult.
class MyPlugin extends CordovaPlugin {
private void saveAndOpenFile() {
/* check permissions
...
*/
}
#Override
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
/* check permission granted and proceed accordingly */
}
}

Related

onRequestPermissionsResult is deprecated in Android Fragment

This is my current (deprecated) method:
int LOCATION_REQUEST_CODE = 10001;
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode == LOCATION_REQUEST_CODE) {
//Permission granted
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
checkSettingsAndStartLocationUpdates();
} else {
//DO SOMETHING - Permission not granted
}
}
}
According to the android documentation https://developer.android.com/training/basics/intents/result I should use registerForActivityResult():
// GetContent creates an ActivityResultLauncher<String> to allow you to pass
// in the mime type you'd like to allow the user to select
ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(),
new ActivityResultCallback<Uri>() {
#Override
public void onActivityResult(Uri uri) {
// Handle the returned Uri
}
});
However I am struggling with replacing my method.
Where do I insert my requestcode and my int array in the new method "registerForActivityResult()" ? Why do I need a "Uri" ?
Where do I insert my requestcode and my int array in the new method
"registerForActivityResult()" ?
You do not have any requestCode. In this design, you can think one single callback for each of the requestCode you have used previously. For handling single permission, you can use the built-in ActivityResultContracts.RequestPermission:
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher, as an instance variable.
private ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(new RequestPermission(), isGranted -> {
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// features requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
});
Why do I need a "Uri" ?
You may get it wrong. In this way, you need an ActivityResultContract<Input, Output> which connects the two Activities. Previously you can only pass Intent to the starting Activity. Now you can pass any type of object using this contract. Input is the type of object you want to pass, Output is the type of result object back from the new Activity. There are some built-in contracts to handle regular scenerio, one of which is GetContent(). If you want a startActivityForResult(intent) like thing, just use ActivityResultContracts.StartActivityForResult, and registering it will return an ActivityResultLauncher, within that intent, you can pass your array with that intent.
// Similar to how you pass your array with intnt before
Intent i = new Intent(MainActivity.this, NewActivity.class);
i.putExtra ("key_arr", arr); // arr is your int array
resultLauncher.launch(i);
I believe you are using the incorrect method.
The example code you gave me shows how to pass image path as a parameter because when dealing with images in android, images are the actual paths to the local image (do you see "mime type" in the comments?)
Since onRequestPermissionsResult is deprecated you are given two choices to resolve the issue.
add #SuppressWarnings("deprecation") to ignore deprecation warning and use it anyway
Or use the New Permission API. Checkout the code
import androidx.activity.result.contract.ActivityResultContracts.*
import androidx.activity.registerForActivityResult
class SampleActivity : AppCompatActivity() {
val requestLocation: () -> Unit = registerForActivityResult(
RequestPermission(), ACCESS_FINE_LOCATION // Permission Type
) { isGranted ->
// to do after permission is granted
}
private fun requestLocationAction() {
requestLocation()
}
}

IllegalStateException while configuration change if call DialogFragment in runnable [duplicate]

I'm getting user reports from my app in the market, delivering the following exception:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyUp(Activity.java:2044)
at android.view.KeyEvent.dispatch(KeyEvent.java:2529)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.widget.TabHost.dispatchKeyEvent(TabHost.java:297)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2880)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2853)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2028)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4028)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
at dalvik.system.NativeStart.main(Native Method)
Apparently it has something to do with a FragmentManager, which I don't use. The stacktrace doesn't show any of my own classes, so I have no idea where this exception occurs and how to prevent it.
For the record: I have a tabhost, and in each tab there is a ActivityGroup switching between Activities.
Please check my answer here. Basically I just had to :
#Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
Don't make the call to super() on the saveInstanceState method. This was messing things up...
This is a known bug in the support package.
If you need to save the instance and add something to your outState Bundle you can use the following:
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
In the end the proper solution was (as seen in the comments) to use :
transaction.commitAllowingStateLoss();
when adding or performing the FragmentTransaction that was causing the Exception.
There are many related problems with a similar error message. Check the second line of this particular stack trace. This exception is specifically related to the call to FragmentManagerImpl.popBackStackImmediate.
This method call, like popBackStack, will always fail with IllegalStateException if the session state has already been saved. Check the source. There is nothing you can do to stop this exception being thrown.
Removing the call to super.onSaveInstanceState will not help.
Creating the Fragment with commitAllowingStateLoss will not help.
Here's how I observed the problem:
There's a form with a submit button.
When the button is clicked a dialog is created and an async process starts.
The user clicks the home key before the process is finished - onSaveInstanceState is called.
The process completes, a callback is made and popBackStackImmediate is attempted.
IllegalStateException is thrown.
Here's what I did to solve it:
As it is not possible to avoid the IllegalStateException in the callback, catch & ignore it.
try {
activity.getSupportFragmentManager().popBackStackImmediate(name);
} catch (IllegalStateException ignored) {
// There's no way to avoid getting this if saveInstanceState has already been called.
}
This is enough to stop the app from crashing. But now the user will restore the app and see that the button they thought they'd pressed hasn't been pressed at all (they think). The form fragment is still showing!
To fix this, when the dialog is created, make some state to indicate the process has started.
progressDialog.show(fragmentManager, TAG);
submitPressed = true;
And save this state in the bundle.
#Override
public void onSaveInstanceState(Bundle outState) {
...
outState.putBoolean(SUBMIT_PRESSED, submitPressed);
}
Don't forget to load it back again in onViewCreated
Then, when resuming, rollback the fragments if submit was previously attempted. This prevents the user from coming back to what seems like an un-submitted form.
#Override
public void onResume() {
super.onResume();
if (submitPressed) {
// no need to try-catch this, because we are not in a callback
activity.getSupportFragmentManager().popBackStackImmediate(name);
submitPressed = false;
}
}
Check if the activity isFinishing() before showing the fragment and pay attention to commitAllowingStateLoss().
Example:
if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
DummyFragment dummyFragment = DummyFragment.newInstance();
ft.add(R.id.dummy_fragment_layout, dummyFragment);
ft.commitAllowingStateLoss();
}
It's October 2017, and Google makes Android Support Library with the new things call Lifecycle component. It provides some new idea for this 'Can not perform this action after onSaveInstanceState' problem.
In short:
Use lifecycle component to determine if it's correct time for popping up your fragment.
Longer version with explain:
why this problem come out?
It's because you are trying to use FragmentManager from your activity(which is going to hold your fragment I suppose?) to commit a transaction for you fragment. Usually this would look like you are trying to do some transaction for an up coming fragment, meanwhile the host activity already call savedInstanceState method(user may happen to touch the home button so the activity calls onStop(), in my case it's the reason)
Usually this problem shouldn't happen -- we always try to load fragment into activity at the very beginning, like the onCreate() method is a perfect place for this. But sometimes this do happen, especially when you can't decide what fragment you will load to that activity, or you are trying to load fragment from an AsyncTask block(or anything will take a little time). The time, before the fragment transaction really happens, but after the activity's onCreate() method, user can do anything. If user press the home button, which triggers the activity's onSavedInstanceState() method, there would be a can not perform this action crash.
If anyone want to see deeper in this issue, I suggest them to take a look at this blog post. It looks deep inside the source code layer and explain a lot about it. Also, it gives the reason that you shouldn't use the commitAllowingStateLoss() method to workaround this crash(trust me it offers nothing good for your code)
How to fix this?
Should I use commitAllowingStateLoss() method to load fragment? Nope you shouldn't;
Should I override onSaveInstanceState method, ignore super method inside it? Nope you shouldn't;
Should I use the magical isFinishing inside activity, to check if the host activity is at the right moment for fragment transaction? Yeah this looks like the right way to do.
Take a look at what Lifecycle component can do.
Basically, Google makes some implementation inside the AppCompatActivity class(and several other base class you should use in your project), which makes it a easier to determine current lifecycle state. Take a look back to our problem: why would this problem happen? It's because we do something at the wrong timing. So we try not to do it, and this problem will be gone.
I code a little for my own project, here is what I do using LifeCycle. I code in Kotlin.
val hostActivity: AppCompatActivity? = null // the activity to host fragments. It's value should be properly initialized.
fun dispatchFragment(frag: Fragment) {
hostActivity?.let {
if(it.lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)){
showFragment(frag)
}
}
}
private fun showFragment(frag: Fragment) {
hostActivity?.let {
Transaction.begin(it, R.id.frag_container)
.show(frag)
.commit()
}
As I show above. I will check the lifecycle state of the host activity. With Lifecycle component within support library, this could be more specific. The code lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED) means, if current state is at least onResume, not later than it? Which makes sure my method won't be execute during some other life state(like onStop).
Is it all done?
Of course not. The code I have shown tells some new way to prevent application from crashing. But if it do go to the state of onStop, that line of code wont do things and thus show nothing on your screen. When users come back to the application, they will see an empty screen, that's the empty host activity showing no fragments at all. It's bad experience(yeah a little bit better than a crash).
So here I wish there could be something nicer: app won't crash if it comes to life state later than onResume, the transaction method is life state aware; besides, the activity will try continue to finished that fragment transaction action, after the user come back to our app.
I add something more to this method:
class FragmentDispatcher(_host: FragmentActivity) : LifecycleObserver {
private val hostActivity: FragmentActivity? = _host
private val lifeCycle: Lifecycle? = _host.lifecycle
private val profilePendingList = mutableListOf<BaseFragment>()
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun resume() {
if (profilePendingList.isNotEmpty()) {
showFragment(profilePendingList.last())
}
}
fun dispatcherFragment(frag: BaseFragment) {
if (lifeCycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) {
showFragment(frag)
} else {
profilePendingList.clear()
profilePendingList.add(frag)
}
}
private fun showFragment(frag: BaseFragment) {
hostActivity?.let {
Transaction.begin(it, R.id.frag_container)
.show(frag)
.commit()
}
}
}
I maintain a list inside this dispatcher class, to store those fragment don't have chance to finish the transaction action. And when user come back from home screen and found there is still fragment waiting to be launched, it will go to the resume() method under the #OnLifecycleEvent(Lifecycle.Event.ON_RESUME) annotation. Now I think it should be working like I expected.
Here is a different solution to this problem.
Using a private member variable you are able to set the returned data as an intent that can then be processed after super.onResume();
Like so:
private Intent mOnActivityResultIntent = null;
#Override
protected void onResume() {
super.onResume();
if(mOnActivityResultIntent != null){
... do things ...
mOnActivityResultIntent = null;
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(data != null){
mOnActivityResultIntent = data;
}
}
Short And working Solution :
Follow Simple Steps
Steps
Step 1 : Override onSaveInstanceState state in respective fragment. And remove super method from it.
#Override
public void onSaveInstanceState( Bundle outState ) {
}
Step 2 : Use
fragmentTransaction.commitAllowingStateLoss( );
instead of fragmentTransaction.commit( ); while fragment operations.
BEWARE, using transaction.commitAllowingStateLoss() could result in a bad experience for the user. For more information on why this exception is thrown, see this post.
I found a dirty solution for this kind of problem. If you still want to keep your ActivityGroups for whatever reason (I had time limitation reasons), you just implement
public void onBackPressed() {}
in your Activity and do some back code in there. even if there is no such Method on older Devices, this Method gets called by newer ones.
Do not use commitAllowingStateLoss(), it should only be used for cases where it is okay for the UI state to change unexpectedly on the user.
https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()
If the transaction happens in ChildFragmentManager of parentFragment, use
parentFragment.isResume() outside to check instead.
if (parentFragment.isResume()) {
DummyFragment dummyFragment = DummyFragment.newInstance();
transaction = childFragmentManager.BeginTransaction();
trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
I had a similar problem, the scenario was like this:
My Activity is adding/replacing list fragments.
Each list fragment has a reference to the activity, to notify the activity when a list item is clicked (observer pattern).
Each list fragment calls setRetainInstance(true); in its onCreate method.
The onCreate method of the activity was like this:
mMainFragment = (SelectionFragment) getSupportFragmentManager()
.findFragmentByTag(MAIN_FRAGMENT_TAG);
if (mMainFragment == null) {
mMainFragment = new SelectionFragment();
mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
R.layout.item_main_menu, getResources().getStringArray(
R.array.main_menu)));
mMainFragment.setOnSelectionChangedListener(this);
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
transaction.commit();
}
The exception was thrown because the when configuration changes (device rotated), the activity is created, the main fragment is retrieved from the history of the fragment manager and at the same time the fragment already has an OLD reference to the destroyed activity
changing the implementation to this solved the problem:
mMainFragment = (SelectionFragment) getSupportFragmentManager()
.findFragmentByTag(MAIN_FRAGMENT_TAG);
if (mMainFragment == null) {
mMainFragment = new SelectionFragment();
mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
R.layout.item_main_menu, getResources().getStringArray(
R.array.main_menu)));
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
transaction.commit();
}
mMainFragment.setOnSelectionChangedListener(this);
you need to set your listeners each time the activity is created to avoid the situation where the fragments have references to old destroyed instances of the activity.
If you inherit from FragmentActivity, you must call the superclass in onActivityResult():
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
...
}
If you don't do this and try to show a fragment dialog box in that method, you may get OP's IllegalStateException. (To be honest, I don't quite understand why the super call fixes the problem. onActivityResult() is called before onResume(), so it should still not be allowed to show a fragment dialog box.)
Fragment transactions should not be executed after Activity.onStop() !
Check that you do not have any callbacks that could execute transaction after onStop(). It is better to fix the reason instead of trying to walk around the problem with approaches like .commitAllowingStateLoss()
Possibly the smoothest and the simplest solution I found in my case was to avoid popping the offending fragment off the stack in response to activity result. So changing this call in my onActivityResult():
popMyFragmentAndMoveOn();
to this:
new Handler(Looper.getMainLooper()).post(new Runnable() {
public void run() {
popMyFragmentAndMoveOn();
}
}
helped in my case.
I was getting this exception when i was pressing back button to cancel intent chooser on my map fragment activity.
I resolved this by replacing the code of onResume(where i was initializing the fragment) to onstart() and the app is working fine.Hope it helps.
Courtesy: Solution for IllegalStateException
This issue had annoyed me for a lot of time but fortunately I came with a concrete solution for it. A detailed explanation of it is here.
Using commitAllowStateloss() might prevent this exception but would lead to UI irregularities.So far we have understood that IllegalStateException is encountered when we try to commit a fragment after the Activity state is lost- so we should just delay the transaction until the state is restored.It can be simply done like this
Declare two private boolean variables
public class MainActivity extends AppCompatActivity {
//Boolean variable to mark if the transaction is safe
private boolean isTransactionSafe;
//Boolean variable to mark if there is any transaction pending
private boolean isTransactionPending;
Now in onPostResume() and onPause we set and unset our boolean variable isTransactionSafe. Idea is to mark trasnsaction safe only when the activity is in foreground so there is no chance of stateloss.
/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
*/
public void onPostResume(){
super.onPostResume();
isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
*/
public void onPause(){
super.onPause();
isTransactionSafe=false;
}
private void commitFragment(){
if(isTransactionSafe) {
MyFragment myFragment = new MyFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frame, myFragment);
fragmentTransaction.commit();
}
}
-What we have done so far will save from IllegalStateException but our transactions will be lost if they are done after the activity moves to background, kind of like commitAllowStateloss(). To help with that we have isTransactionPending boolean variable
public void onPostResume(){
super.onPostResume();
isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
if (isTransactionPending) {
commitFragment();
}
}
private void commitFragment(){
if(isTransactionSafe) {
MyFragment myFragment = new MyFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frame, myFragment);
fragmentTransaction.commit();
isTransactionPending=false;
}else {
/*
If any transaction is not done because the activity is in background. We set the
isTransactionPending variable to true so that we can pick this up when we come back to
foreground
*/
isTransactionPending=true;
}
}
I think using transaction.commitAllowingStateLoss(); is not best solution.
This exception will be thrown when activity's configuration changed and fragment onSavedInstanceState() is called and thereafter your async callback method tries to commit fragment.
Simple solution could be check whether activity is changing configuration or not
e.g. check isChangingConfigurations()
i.e.
if(!isChangingConfigurations()) {
//commit transaction.
}
Checkout this link as well
Whenever you are trying to load a fragment in your activity make sure that activity is in resume and not going to pause state.In pause state you may end up losing commit operation that is done.
You can use transaction.commitAllowingStateLoss() instead of transaction.commit() to load fragment
or
Create a boolean and check if activity is not going to onpause
#Override
public void onResume() {
super.onResume();
mIsResumed = true;
}
#Override
public void onPause() {
mIsResumed = false;
super.onPause();
}
then while loading fragment check
if(mIsResumed){
//load the your fragment
}
If you are doing some FragmentTransaction in onActivityResult what you can do you can set some boolean value inside onActivityResult then in onResume you can do your FragmentTransaction on the basis of the boolean value. Please refer the code below.
#Override
protected void onResume() {
super.onResume;
if(isSwitchFragment){
isSwitchFragment=false;
bottomNavigationView.getTabAt(POS_FEED).select();
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FilterActivity.FILTER_REQUEST_EVENT && data != null) {
isSwitchFragment=true;
}
}
In regards to #Anthonyeef great answer, here is a sample code in Java:
private boolean shouldShowFragmentInOnResume;
private void someMethodThatShowsTheFragment() {
if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
showFragment();
} else {
shouldShowFragmentInOnResume = true;
}
}
private void showFragment() {
//Your code here
}
#Override
protected void onResume() {
super.onResume();
if (shouldShowFragmentInOnResume) {
shouldShowFragmentInOnResume = false;
showFragment();
}
}
The exception is threw here (In FragmentActivity):
#Override
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
super.onBackPressed();
}
}
In FragmentManager.popBackStatckImmediate(),FragmentManager.checkStateLoss() is called firstly. That's the cause of IllegalStateException. See the implementation below:
private void checkStateLoss() {
if (mStateSaved) { // Boom!
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
}
I solve this problem simply by using a flag to mark Activity's current status. Here's my solution:
public class MainActivity extends AppCompatActivity {
/**
* A flag that marks whether current Activity has saved its instance state
*/
private boolean mHasSaveInstanceState;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
mHasSaveInstanceState = true;
super.onSaveInstanceState(outState);
}
#Override
protected void onResume() {
super.onResume();
mHasSaveInstanceState = false;
}
#Override
public void onBackPressed() {
if (!mHasSaveInstanceState) {
// avoid FragmentManager.checkStateLoss()'s throwing IllegalStateException
super.onBackPressed();
}
}
}
If you have crash with popBackStack() or popBackStackImmediate() method please try fixt with:
if (!fragmentManager.isStateSaved()) {
fragmentManager.popBackStackImmediate();
}
This is worked for me as well.
In my case I got this error in an override method called onActivityResult. After digging I just figure out maybe I needed to call 'super' before.
I added it and it just worked
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data); //<--- THIS IS THE SUPPER CALL
if (resultCode == Activity.RESULT_OK && requestCode == 0) {
mostrarFragment(FiltroFragment.newInstance())
}
}
Maybe you just need to add a 'super' on any override you are doing before your code.
Kotlin extension
fun FragmentManager?.replaceAndAddToBackStack(
#IdRes containerViewId: Int,
fragment: () -> Fragment,
tag: String
) {
// Find and synchronously remove a fragment with the same tag.
// The second transaction must start after the first has finished.
this?.findFragmentByTag(tag)?.let {
beginTransaction().remove(it).commitNow()
}
// Add a fragment.
this?.beginTransaction()?.run {
replace(containerViewId, fragment, tag)
// The next line will add the fragment to a back stack.
// Remove if not needed.
// You can use null instead of tag, but tag is needed for popBackStack(),
// see https://stackoverflow.com/a/59158254/2914140
addToBackStack(tag)
}?.commitAllowingStateLoss()
}
Usage:
val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
Starting from support library version 24.0.0 you can call FragmentTransaction.commitNow() method which commits this transaction synchronously instead of calling commit() followed by executePendingTransactions(). As documentation says this approach even better:
Calling commitNow is preferable to calling commit() followed by executePendingTransactions() as the latter will have the side effect of attempting to commit all currently pending transactions whether that is the desired behavior or not.
I know there is an accepted answer by #Ovidiu Latcu but after some while, error still persist.
#Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
Crashlytics still sending me this weird error message.
However error now occurring only on version 7+ (Nougat)
My fix was to use commitAllowingStateLoss() instead of commit() at the fragmentTransaction.
This post is helpful for commitAllowingStateLoss() and never had a fragment issue ever again.
To sum it up, the accepted answer here might work on pre Nougat android versions.
This might save someone a few hours of searching.
happy codings. <3 cheers
To bypass this issue, we can use The Navigation Architecture Component , which was introduced in Google I/O 2018.
The Navigation Architecture Component simplifies the implementation of navigation in an Android app.
change getFragmentManager() to getChildFragmentManager(). Don't use parent FragmentManager, try to use self.
Add this in your activity
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (outState.isEmpty()) {
// Work-around for a pre-Android 4.2 bug
outState.putBoolean("bug:fix", true);
}
}
I have also experienced this issue and problem occurs every time when context of your FragmentActivity gets changed (e.g. Screen orientation is changed, etc.). So the best fix for it is to update context from your FragmentActivity.
I ended up with creating a base fragment and make all fragments in my app extend it
public class BaseFragment extends Fragment {
private boolean mStateSaved;
#CallSuper
#Override
public void onSaveInstanceState(Bundle outState) {
mStateSaved = true;
super.onSaveInstanceState(outState);
}
/**
* Version of {#link #show(FragmentManager, String)} that no-ops when an IllegalStateException
* would otherwise occur.
*/
public void showAllowingStateLoss(FragmentManager manager, String tag) {
// API 26 added this convenient method
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (manager.isStateSaved()) {
return;
}
}
if (mStateSaved) {
return;
}
show(manager, tag);
}
}
Then when I try to show a fragment I use showAllowingStateLoss instead of show
like this:
MyFragment.newInstance()
.showAllowingStateLoss(getFragmentManager(), MY_FRAGMENT.TAG);
I came up to this solution from this PR: https://github.com/googlesamples/easypermissions/pull/170/files

Environment.getExternalStorageDirectory() return null

I m trying to get the Internal Storage path of a Android Device.
Since most of the devices late off return Internal Storage path using Environment.getExternalStorageDirectory().getPath(); so I am using this to get the path.
Every thing is working fine except that when I am calling Environment.getExternalStorageDirectory().getPath(); from an non Activity class it is returning null whereas if I call it from a Activity class it returns the correct path.
I Tried searching other posts but could not find anything useful.
Any help would be really grateful.
EDIT:
if(getExtSdCardPath(con)!=null)
{ path=getExtSdCardPath(con);
if(new File(path).getPath().equal(Environment.getExternalStorageDirectory().getPath())) // This line give null "Null Pointer exception"
{
return null;
}
return path;
}
I am checking if the SD Card path is same as the path which is returned by Environment.getExternalStorageDirectory().getPath()
Ideally, getExtSdCardPath() would be "idempotent", which is a fancy way of saying "does the same work and returns the same thing no matter how many times you call it".
In your case, it is not. The first call to getExtSdCardPath() is returning the value that you want, and the second call to getExtSdCardPath() is returning null.
In your case, there is no particular need to call getExtSdCardPath() twice, and so you can work around the idempotence issue by rewriting your code to be something like:
path=getExtSdCardPath(con);
if(path!=null)
{
if(new File(path).getPath().equal(Environment.getExternalStorageDirectory().getPath())) // This line give null "Null Pointer exception"
{
return null;
}
return path;
}
Sounds like you forgot to put the requested permission in the manifest OR/AND forgot to ask for such a permission in runtime (in case you run this on devices with android 6.0 and above).
try to addto your manifest : <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Here a quick and simple implementation example of how to request the permission in runtime:
public class MainActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback{
private static final int REQUEST_WRITE_PERMISSION = 111; //Number is not matter, just put what you want
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_WRITE_PERMISSION && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//Do your stuff with the file
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermission();
}
private void requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_PERMISSION);
} else {
//Do your stuff with the file
}
}
}

Request runtime permissions from v4.Fragment and have callback go to Fragment?

I'm having a weird issue that is causing a conflict. I had to switch to native Fragments to fix it, but there are bugs with that.
My original problem: I have a navigation drawer setup with v4 Fragments. To ask for permission in one of my Fragments I call ActivityCompat.requestPermissions(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION, 1); The prompt shows up just fine, but when I accept or deny the permission, nothing happens. The callback onRequestPermissionsResult() is never called. Instead it gets called in the Activity that my Fragments are attached to. Useless to me, I need the callback to work in the Fragment.
With this in mind I was told that I need to use FragmentCompat, but that only works with native Fragments (v13+), so I changed navigation drawer to work from native Fragments instead of the v4 support library Fragments. However, because I'm using AppCompatActivity, certain things do not work, like addToBackStack() and going back to a previous fragment.
Long story short, does anyone know how I can use the v4.Fragment and still call for permission in the Fragment and get the callback to be in the Fragment? I feel like this is a bug in Android that hasn't been addressed but I'm not 100%.
Let me know if you need to see my code, it's just the standard methods that you need for runtime permissions, I would like to work with v4 Fragments though which doesn't work from my understanding.
Adding this to the parent activity works for me:
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
List<Fragment> fragments = getSupportFragmentManager().getFragments();
if (fragments != null) {
for (Fragment fragment : fragments) {
fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
Source: https://code.google.com/p/android/issues/detail?id=189121#c5
If you need to get the permissionResult in fragment v4 make sure you use
Fragment.requestPermission(String[], int);
instead of
AppCompat.requestPermission(Activity, String[], int)
Check out this answer!
This behavior seems to be present in the v4 Fragment support class requestPermissions in Fragment. The Activity/FragmentCompat implementations exist for people who wish to use the native classes with the extended functionality on API levels between 11 and 23.
You can use this part of code
requestPermissions(new String[]{Manifest.permission.GET_ACCOUNTS}, PERMISSION_REQUEST_CODE);
I faced the same situation recently, when I needed to check for a permission inside the support fragment and get a callback there.
I was able to use ContextCompat to checkSelfPermission and then as #NasaGeek said called android.support.v4.app.Fragment's requestPermissions to request the permission and then got a call back to onRequestPermissionsResult in v4 Fragment.
Hope this helps.
Thanks.
v4.Fragment works well. I have an issue with nested fragment of v4.Fragment. Permission is asked, but the callback onRequestPermissionsResult() is never called in nested fragment!
Issue opened
At the moment the most stable solution is doing it by hand. I myself resolved it simply by notifying child fragments from the parent fragments.
if (fragment.getParentFragment() != null) {
Fragment parentFragment = fragment.getParentFragment();
try {
ChildFragmentPermissionRegistry registry = (ChildFragmentPermissionRegistry) parentFragment;
registry.registerPermissionListener((ChildFragmentPermissionCallback) fragment);
parentFragment.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_REQUEST_CODE);
} catch (ClassCastException e) {
Log.e(PermissionVerifier.class.getSimpleName(), e.getMessage());
}
} else {
fragment.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_REQUEST_CODE);
}
Where parent fragment implements interface ChildFragmentPermissionRegistry and registers child fragment,
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (callback != null) {
callback.onRequestPermissionsResult(requestCode, permissions, grantResults);
callback = null;
}
}
and child fragments implements ChildFragmentPermissionCallback
and interfaces are something like this:
public interface ChildFragmentPermissionCallback {
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
}
public interface ChildFragmentPermissionRegistry {
void registerPermissionListener(ChildFragmentPermissionCallback callback);
}
I don't know if it's recently fixed by google, but I can reach the expected result without doing any tricks.
The most important thing is to call super.onRequestPermissionsResult(requestCode, permissions, grantResults); in the activity, so it will pass the result to fragment if it's requested from fragment.
So, what I do is:
1) in fragment, ask permission using v4 fragment.requestPermissions(permissions, requestCode)
2) in activity's onRequestPermissionsResult, must call
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
3) in fragment's onRequestPermissionsResult, write the code I want to handle the result.
In my case I have requested the permission from the fragment and also need to get the response in fragment.
But my phone running on android 8.1
so I was need to add one more condition for check this
so eventually there is my solution
private void doOnWazeButtonClick()
{
int permissionStatus = PackageManager.PERMISSION_DENIED;
if (getContext() != null)
{
permissionStatus = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION);
}
if (permissionStatus == PackageManager.PERMISSION_GRANTED)
{
showContentWaze();
}
else
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
Objects.requireNonNull(getActivity()).requestPermissions(new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_CODE_PERMISSION_ACCESS_FINE_LOCATION);
}
else
{
requestPermissions(new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_CODE_PERMISSION_ACCESS_FINE_LOCATION);
}
}
}
Check runtime permission from Fragment (the 2021 way)
I have answered this in a different question, here is the link: answer link.
In short, we can now use registerForActivityResult in a fragment. In your fragment's constructor (before onCreate), you initialize ActivityResultLauncher<String[]> activityResultLauncher resultLauncher, and when you need permission, simply invoke resultLauncher.launch(stringArrayOfPermissions). Please check the link on the first line for detailed example.
Just use requestPermission("your permission/s in string array",your request code) simply no need to use Fragment.requestPermissons(String[],int );
This method in your fragment calls requestPermissions of android.support.v4.app.Fragment class i.e
public final void requestPermissions(#NonNull String[] permissions, int requestCode) {
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
}

Appcelerator custom module receiving callback for onActivityResult

I'm trying to create a custom module in Appcelerator for the new Square API for Android. I have everything the way I want it, but the main problem is that I want to be able to notify the caller that the payment was successful for if it failed. The Square API says this:
After Square finishes, Android invokes Activity.onActivityResult() on the activity passed to the constructor. The request code passed to this method will be passed to onActivityResult(). The result code is Activity.RESULT_CANCELED if the payment was canceled or Activity.RESULT_OK if the payment succeeded.
I've been passing the TiContext.currentActivity to the constructor:
public SquareModule(TiContext tiContext) {
super(tiContext);
ourSquare = new Square(tiContext.getActivity());
}
And then in the method that actually runs the payment, I have this that basically tries to set the passed in callback to the onResult handlers of the current activity using the registerResultHandler in the TiActivitySupportHelper class.
public void runPayment(KrollInvocation invocation, int price, String description, KrollCallback handler) {
Log.i(LCAT, "runPayment called");
// Register the passed in function as a handler on the onResult stack
this.resultCallback = handler;
Activity activity = invocation.getTiContext().getActivity();
TiActivitySupportHelper support = new TiActivitySupportHelper(activity);
int code = support.getUniqueResultCode();
support.registerResultHandler(code, this);
// Some of the payment work here
ourSquare.squareUp(Bill.containing(advice), code);
}
The main module class implements TiActivityResultHandler and implements onResult and onError. These methods are not being called at all. And of course the passed in method isn't being called either.
For completeness, see the implementation of the onResult and onError handlers:
#Override
public void onResult(Activity activity, int requestCode, int resultCode, Intent data)
{
Log.i(LCAT, "onResult Called");
if (resultCallback == null) return;
KrollDict event = new KrollDict();
event.put(TiC.EVENT_PROPERTY_REQUEST_CODE, requestCode);
event.put(TiC.EVENT_PROPERTY_RESULT_CODE, resultCode);
event.put(TiC.EVENT_PROPERTY_INTENT, new IntentProxy(getTiContext(), data));
event.put(TiC.EVENT_PROPERTY_SOURCE, this);
resultCallback.callAsync(event);
}
#Override
public void onError(Activity activity, int requestCode, Exception e)
{
Log.i(LCAT, "onError Called");
if (resultCallback == null) return;
KrollDict event = new KrollDict();
event.put(TiC.EVENT_PROPERTY_REQUEST_CODE, requestCode);
event.put(TiC.EVENT_PROPERTY_ERROR, e.getMessage());
event.put(TiC.EVENT_PROPERTY_SOURCE, this);
resultCallback.callAsync(event);
}
And also see the Appcelerator JS calling the method in the module:
square.runPayment(2, 'Testing123', function(e) {
label1.text = 'Payment Successful!';
});
For those that come upon this question. The answer can be found in the module here:
https://github.com/hidef/Appcelerator-Square-Module (see the LaunchSquare.java class)
Basically, I used an Activity object that I created to receive the Square API's onResult update. I then was able to pass that back cleanly to the module class and hand it back via callback to the calling application.

Categories