Activity resets on Fragment Transaction - java

I have an activity that loads multiple fragments depending on user action (button click) or event (FCM Data Message which triggers a LocalBroadcast).
I hit a snag recently when I put a Fragment Transaction inside a BroadcastReceiver, and as soon as the receiver gets triggered, instead of loading up the next fragment, I get the first (default) fragment which is loaded in the OnCreate of the activity, implying that the Activity has reset/restarted somehow.
Given the speed of this, the only error I managed to see before the logcat on Android Studio reset was this :
java.lang.IllegalStateException: Can not perform this action after
onSaveInstanceState
Digging around hasn't helped much, except for this article on Activity State Loss which I discovered. It is rather old (2013) but seems to make sense. However, there is no solution that I can think of, short of making my fragment a bit more complicated, and handing the next fragment's logic in this one itself.
Please find the bit of code where this happens below.
BroadcastReceiver assistanceReceivedStatusReceiver = new BroadcastReceiver() {
public void dummyfunc(){
return;
}
#Override
public void onReceive(Context context, Intent intent) {
// this is triggered by the localBroadcast from FCM Service
boolean requestresult = intent.getBooleanExtra("success", true);
if(!requestresult) {
// we don't have a responder
Log.d(TAG, "onReceive: =======================================");
Log.d(TAG, "onReceive: =======================================");
Log.d(TAG, "onReceive: UNABLE TO FIND A RESPONDER");
Log.d(TAG, "onReceive: =======================================");
Log.d(TAG, "onReceive: =======================================");
String message = "Unable to find you a responder, please try again!";
frameAnimation.stop();
txtRequestStatus.setText(message);
dialogButtonLayout.setVisibility(View.VISIBLE);
showBottomAppBar();
showMenuFab();
moveMenuRight();
setMenuImage(R.drawable.baseline_undo_white_24dp);
menuButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
dialogLayout.setVisibility(View.GONE);
waitingLayout.setVisibility(View.VISIBLE);
moveMenuCenter();
resetMenuImage();
menuButton.setOnClickListener(defaultMenuButtonListener);
}
});
} else {
// we have a responder
// this is a one time receiver - set up an Observable for the Live<Incident>
// and unregister self.
Log.d(TAG, "onReceive: =======================================");
Log.d(TAG, "onReceive: =======================================");
Log.d(TAG, "onReceive: RECEIVED A RESPONDER");
Log.d(TAG, "onReceive: =======================================");
Log.d(TAG, "onReceive: =======================================");
inIncident = true;
Bundle nextbundle = new Bundle();
responderinfo = intent.getBundleExtra("responderinfo");
nextbundle.putParcelable("data", intent.getBundleExtra("data"));
nextbundle.putBundle("responderinfo", responderinfo);
// GO! GO! GO!!!
//startFragmentWithArgs(new RequestAssistFragmentDeliver(),nextbundle );
RequestAssistFragmentDeliver deliver = new RequestAssistFragmentDeliver();
deliver.setArguments(nextbundle);
((Reviv) getActivity()).getSupportFragmentManager()
.beginTransaction()
.replace(R.id.containerFrameLayout, deliver).commitAllowingStateLoss();
}
}
};
Any ideas on what's going wrong? As always, I'm happy to share more info based on what is needed (the code base is humongous, and knowing what is needed helps me share the relevant segments).
UPDATE 1 :
Sharing the functions as requested by Udit. These are wrapper functions, to help make the code a little more readable. The bottomAppBar (BottomAppBar) and menuButton (FAB) are views that are loaded in the Activity, and I make associations in each Fragment by calling a getter defined in the Activity.
(MainActivity)getActivity.getBottomAppBar();
Functions:
private void showBottomAppBar(){
bottomAppBar.setVisibility(View.VISIBLE);
menuButton.setVisibility(View.VISIBLE);
}
private void moveMenuRight(){
bottomAppBar.setFabAlignmentMode(BottomAppBar.FAB_ALIGNMENT_MODE_END);
}
private void showMenuFab(){
//bottomAppBar.setFabAttached(true);
menuButton.setVisibility(View.VISIBLE);
}

From what I can figure out, your activity is restarting because of the crash while adding the fragment (java.lang.IllegalStateException).
To confirm, you can replace
.commit()
method with
.commitAllowingStateLoss()
and see if this solves your problem

Related

Upload data to firestore before activity is completely destroyed

I am making a quiz app. And I want that if the user kills the app their performance be uploaded on the firestore. When he clicks submit Button everything works fine but when I call submitbutton.callOnClick();
in onDestroy() some code is executed but the data is not uploaded.
this is my onDestroy() code
protected void onDestroy() {
Log.d(TAG, "onDestroy: ------");
if (clicked) { }
else
{
clicked = false;
submitButton.callOnClick();
}
Log.d(TAG, "onDestroy: done");
super.onDestroy();
And this is some code of submitButton 's onClickListener
Log.d(TAG, "onClick: ----------------------");
Result result=new Result(new String(ansString),new String(officialAnsString),correct,incorrect,not_attempted,marks);
FireBase_Variables.db.collection(tName+"_result").document(currentUser.getUid()).set(result);
Log.d(TAG, "onClick: ----------------------");
I can see the upper log in the logcat but not the lower one. Which means the line for uploading data is not executed.
What should I do?
You'll want to wait until the write operation is completed, before calling super.onDestroy().
If we unroll the code into a single block, that'd be something like:
protected void onDestroy() {
Log.d(TAG, "onDestroy: ------");
if (clicked) { }
else
{
clicked = false;
Log.d(TAG, "onClick: ----------------------");
Result result=new Result(new String(ansString),new String(officialAnsString),correct,incorrect,not_attempted,marks);
FireBase_Variables.db.collection(tName+"_result").document(currentUser.getUid()).set(result).addOnCompleteListener(new new OnCompleteListener<Void>() {
#Override
public void onComplete(Void aVoid) {
Log.d(TAG, "onDestroy: done");
super.onDestroy();
}
});
}
Also see: How to check a certain data already exists in firestore or not
Note: I didn't compile/run this code, so there may be syntactic problems with it. If you encounter such problems, please try to solve them for yourself and use the edit link under the answer to fix them for folks who come here after you.
In general you'll want to do the above somewhere else than in an onDestroy handler though, as that method is fairly unlikely to be called.

LocalBroadcastManager not receiving messages correctly

I have a, what I thought was a, simple task. I have a list of achievements in a recyclerView in my MainActivity. Clicking on one of the achievements launches the AchievementDetailActivity. You do an action in the AchievementDetailActivity and it completes the achievement. When that happens, I need the thumbnail on the MainActivity to be the achievement icon and not the locked icon. What I thought was I could just use the LocalBroadcastManager to send and receive messages from one activity to another, but that isn't working. From everything I have read you are supposed to unregister the listener in your activity onPause and onStop lifecycle methods, however, if you unregister the listener in the onPause of MainActivity, it gets unregistered when the AchievementDetailActivity starts and nothing gets delivered to the MainActivity. I don't understand how you can use LocalBroadCastManager with receivers to send information between activities when they get unregistered as soon as you start a new activity. The following example shows it unregisters as soon as the second activity is started, so the first activity will never get the broadcast...
public class MainActivity extends AppCompatActivity {
public static final String ACTION = "update";
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("MAIN ACTIVITY", "RECEIVED EVENT");
}
}
public void onStart() {
super.onStart();
LocalBroadcastManager.getInstance(this).registerReceiver(receiver), new IntentFilter(ACTION));
}
public void onPause() {
Log.d("MAIN ACTIVITY", "REMOVING LISTENER");
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
}
//.. The rest of MainActivity
}
public class SecondActivity extends AppCompatActivity {
#Override
public void onStart() {
super.onStart();
//initialize view and such
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getApplicationContext());
Intent intent = new Intent(MainActivity.ACTION);
intent.putExtra("Something", "somewhere");
manager.sendBroadcast(intent);
}
)};
}
If you run the above, obviously with the buttons and such, you will get a message that shows it unregisters the listener as soon as SecondActivity starts and the MainActivity will never get the message it is supposed to get when you click a button on the second activity.
Is there a way to send information from activity to activity, or because of the lifecycle events is that not possible? Everything I read said LocalBroadcastManager was the right way to do it, but it gets unregistered so, how can it? Please help, this is driving me nuts. Thank you.
If you want to use your LocalBroadcastManager to fetch results, do not unregister in onPause, in which case must to unregister in onDestroy().
If you startAvtivity only to fetch some results, it is a good way to startActivityForResult.
Its not a great idea to use LocalBroadcastReceiver for this purpose. Use startActivityForResult instead. Follow this Official doc for implementation details.
The reason its not wokring is:
You are registering the receiver in onStart and unregistering it in onPause(). When your second activity is shown, onPause () will be called as a result the BroadcastReceiver is unregistered.
Its not a great idea to keep it registered since it would lead to memory leaks.

Android return value from AccessibilityService

i have this AccessibilityService class for the USSD
public class USSDService extends AccessibilityService {
public static String TAG = "USSDService";
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(TAG, "onAccessibilityEvent");
String text = event.getText().toString();
if (event.getClassName().equals("android.app.AlertDialog")) {
performGlobalAction(GLOBAL_ACTION_BACK);
Log.d(TAG, text);
Intent intent = new Intent("com.times.ussd.action.REFRESH");
intent.putExtra("message", text);//this is the value that i want to return to MainActivity
Toast.makeText (this,text,Toast.LENGTH_LONG).show();
}
}
#Override
public void onInterrupt() {
}
#Override
protected void onServiceConnected() {
super.onServiceConnected();
Log.d(TAG, "onServiceConnected");
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.flags = AccessibilityServiceInfo.DEFAULT;
info.packageNames = new String[]{"com.android.phone"};
info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
setServiceInfo(info);
}}
and i invoke in main activity like this
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
////////////////USSD////////////////////
Intent mIntent = new Intent(this, USSDService.class);
startService(mIntent);
}
and this is a function that dial ussd
private void dailNumber(String code) {
String ussdCode = "*" + code + Uri.encode("#");
Intent callIntent = new Intent(Intent.ACTION_CALL)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
callIntent.setData(Uri.parse("tel:" + ussdCode));
callIntent.putExtra("simSlot", 0);
startActivity(callIntent);
}
when i call this function by clicking a button, every thing is ok and the response of ussd call show in Toast as i code it in function onAccessibilityEvent
what i want is return the response value to MainActivity
Accessibility services cannot be started in the manner you are attempting to start them. They can only be managed from the Accessibility Settings menu. This is for good reason. Accessibility services can read screen contents. OH, you downloaded my app, that's nice. NOW I'm going to launch an Accessibility Service that watches all of your screen content for things that look like Credit Card numbers.
Intent mIntent = new Intent(this, USSDService.class);
startService(mIntent);
The lines of code above ARE NOT accomplishing what you think they are. The accessibility service will launch (call your service's onCreate), but it will launch in a functionally limited state that does not allow it to bind to applications and the Android Operating System in the way an Accessibility Service normally would.
Once properly configured, go to Settings -> Accessibility and find your service listed next to TalkBack. Then turn it on. Once you do this, in order to communicate between your two processes you're going to need some type of interprocess communication mechanism. For simple communication you can consider using SharePreferences. Otherwise, you're probably going to have to look into BroadCastReceivers.
See how far that gets you, and ask another question if you need!

Android memory leak and can I be sure if I really have one

I'm new to Android (just completed my first class) and am struggling with a memory leak issue that I'm not sure is a real issue or not. I've taken the sample code for reading the contacts database that can be downloaded via the "download sample" button on the Android Developer site here:
https://developer.android.com/training/contacts-provider/retrieve-details.html
The app runs fine on its own (API 22 or less due to the READ_CONTACTS permission changes). I want to use the main activity in the sample in my own app as a child of the main activity to pick a contact and pass back its URI to be used to store some custom contact info. I'm using startActivityForResult() tied to a menu icon to launch the activity as a child of my main activity. I've changed very little in the sample code aside from executing finish() when a contact is clicked rather than launching the detail activity in the sample app:
Original:
#Override
public void onContactSelected(Uri contactUri) {
if (isTwoPaneLayout && mContactDetailFragment != null) {
// If two pane layout then update the detail fragment to show the selected contact
mContactDetailFragment.setContact(contactUri);
} else {
// Otherwise single pane layout, start a new ContactDetailActivity with
// the contact Uri
Intent intent = new Intent(this, ContactDetailActivity.class);
intent.setData(contactUri);
startActivity(intent);
}
}
New:
#Override
public void onContactSelected(Uri contactUri) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setData(contactUri);
Log.d(LOG_TAG, contactUri.toString());
setResult(RESULT_OK, intent);
finish();
}
The UI functionality is working fine and I get the URI as expected on the callback. However, StrictMode is enabled and reports that there is a new instance of the child activity each time I call it. This doesn't happen in the original app even with multiple orientation changes. I've tried many things to eliminate this issue in my new app to no avail. Here's what I've tried so far:
I tried setting the intent flags FLAG_ACTIVITY_REORDER_TO_FRONT and FLAG_ACTIVITY_CLEAR_TOP.
I tried forcing garbage collection prior to each new call to the activity:
if (BuildConfig.DEBUG)
{
System.gc();
}
startActivityForResult(intent, 1);
I removed the fragment definition from the XML layout and replace any existing instance via the FragmentManager:
if (getSupportFragmentManager().findFragmentByTag(ADDCONTACTSFRAGMENT_TAG) == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.add_contacts_container, new AddContactsFragment(), ADDCONTACTSFRAGMENT_TAG)
.commit();
}
I read that any listeners not re-initialized to null could cause a leak, so I've attempted to create a member variable for all of them and reset them in the onDestroyView and onDestroy methods of the child activity fragment:
#Override
public void onDestroy() {
super.onDestroy();
if (mCursor != null) {
mCursor.close();
mCursor = null;
}
mAdapter.swapCursor(null);
mAdapter = null;
mLoader = null;
mContext = null;
Log.d(LOG_TAG, "In onDestroy()");
}
#Override
public void onDestroyView() {
super.onDestroyView();
getListView().setOnItemClickListener(null);
getListView().setOnScrollListener(null);
mSearchView.setOnQueryTextListener(null);
if (Utils.hasICS()) {
// This listener added in ICS
MenuItemCompat.setOnActionExpandListener(mSearchItem, null);
}
mSearchTerm = null;
mSearchItem = null;
mSearchView = null;
//setListAdapter(null);
mView = null;
mOnContactSelectedListener = null;
mDialogClickListener = null;
mImageLoader.setPauseWork(false);
mImageLoader = null;
if (mAdapterView != null) {
mAdapterView.setOnItemClickListener(null);
mClickView.setOnClickListener(null);
mClickView = null;
mAdapterView = null;
}
Log.d(LOG_TAG, "In onDestroyView()");
}
I also found several examples of an unbindDrawables() method that was said to be necessary to remove all the View objects, but it doesn't work for an AdapterView, which this fragment is. Could this be the problem?
I've used both the Allocation Tracking tab in the Device Monitor and heap analysis via the stand-alone MAT application to verify that there are in fact multiple instances of the child fragment in memory, but I'm not sure at this point if this is really a leak or if garbage collection just won't remove the fragment instances until memory is constrained. Any advice would be greatly appreciated.
There is an Android library that could help to properly analyse memory leaks: leakcanary

Recurring background task Android - Handler vs AlarmManager

I am working on an application for research purposes. We want to track some user activity on the phone: if some actions are easily detactable with broadcast receivers, we want also to check current running applications.
This application works only on Android devices that runs android 5.0 or lower.
My problem is that I post a Runnable Obj in a handler, Runnable posts again itself in handler after HALF_SECOND (see code for details). In runnable I get information and send them to IntentService to perform work.
Everything works fine: app starts at boot, handler and runnable do their job in background UNLESS I open the main Activity.
The app is able to keep going for days, but if I open the main Activity and then close it from "recent open activities" with a swipe, or from the memory task manager, handler and runnable stop, even if they are not called/accessed by the activity (they are in a Separate Service).
Moreover, not always a call to onDestroy (of the activity or Service) is made.
Reading online I understand that swipe or task manager remove the app from memory abrouptly thus not always calling onDestory.
What I want to achive is to make the handler start again soon after the main activity is closed.
What I have tried is to put some check in onPause method of the activity, making sure to remove this check if onStart is called again (like in case the the app switches from vertical to horizontal layout, or if home button is pressed and then app is opend again). Also implemented a way to make the handler send "ImAlive" intent to a broadcast receiver, which should restart the service that starts the handler, if intents do not arrive before a count down is finished. Unfortunately, as soon the main activty stops existing, even the broadcast is automatically unregistered and destroyed.
My question is, is there a way to create something that is able to make my handler restart if the activity is closed? Or is there some other pattern that can help me as workaround for what I want to achieve? Because I am polling data every half second I read is better to use handler, because Timer augments small interval to a greater interval, and AlarmManager is not precise enough for very small interval.
What I want to achieve is something similar to Facebook, Instagram, Whatsapp, Telegram app, that are always in memory, and even if you force to terminate them, after a few seconds are back again there... how?
We are not interested in battery issues because of continuous polling to data. As for research purposes we don't mind if the phone on which we are testing last 2 days straight, 1 day or 12 hours or less.
Here the code: OnBootService is started from broadcast receiver, declared in manifest when onBootCompleted and ShutDown actions are received, in order to start and stop handler.
public class OnBootService extends Service{
private static final Handler handler = new Handler();
private final long HALF_SEC = 500;
private RunnableTest r = null;
private Context myContext = this;
private final String TAG = "BootService";
// Extras
public static final String START = "start";
public static final String STOP = "stop";
#Override
public IBinder onBind(Intent intent){
return null;
}
#Override
public int onStartCommand(Intent intent, int flag, int startId){
String action = intent.getAction();
switch(action){
case START: startHandler();
break;
case STOP: stopHandler();
break;
}
return START_NOT_STICKY;
}
private void startHandler(){
if(r == null){
r = new RunnableTest();
handler.post(r);
Log.i(TAG, "----Handler started!");
}
}
private void stopHandler(){
if(r != null){
Log.i(TAG, "----calling STOP");
handler.removeCallbacks(r);
r = null;
}
}
private class RunnableTest implements Runnable {
private String TAG = "RunnableTest";
public RunnableTest(){}
#Override
public void run(){
handler.removeCallbacks(this);
// Do stuff
Intent i = new Intent(myContext, MyIntentService.class);
i.putExtra("addStuff", myStuff);
myContext.startService(i);
handler.postDelayed(this, HALF_SEC);
}
}
Activity is empty: all method overridden just to understand proper Activity lifecycle, but else is empty for now.
public class MainActivity extends AppCompatActivity {
private final String TAG = "Activity";
private Context myContext = this;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// access a file and get stored information to show
setContentView(R.layout.activity_main);
Toast.makeText(getBaseContext(), "Application open successfully", Toast.LENGTH_LONG).show();
}
#Override
protected void onRestart(){
super.onRestart();
Log.e(TAG, "----onRestart Called");
}
#Override
protected void onStart(){
super.onStart();
Log.e(TAG, "----onSTART Called");
}
#Override
protected void onResume(){
super.onResume();
Log.e(TAG, "----onRESUME Called");
}
#Override
protected void onPause(){
super.onPause();
Log.e(TAG, "----onPAUSE Called");
}
#Override
protected void onStop(){
super.onStop();
Log.e(TAG, "----onSTOP Called");
}
#Override
protected void onDestroy(){
super.onDestroy();
Log.e(TAG, "----onDestroy Called");
}
}
Any help is really appreciated, if you need some more information on the code, I will update the post.
Thank you!
Android system can restart the service if u return START_STICKY inside onStartCommand().
It works perfectly on all lower version than Lollipop in Android.
No need of CountDownTimer.

Categories