I have two activities my MainActivity and some other one called DetailActivity. When the app is first started it opens MainActivity and there it binds DataService and makes a call to fetch some data and populate a list view.
From that list view user has a button to open so called detail view for every item in the list. Opening that detail view means starting the second activity (DetailActivity).
Its done like this:
final Intent intent = new Intent(getContext(), DetailActivity.class);
intent.putExtra("data", dto);
getContext().startActivity(intent);
When second one is opened user is able to go back either by using back button (one left of home button on android) or by clicking back arrow in the header.
Everything works as expected except that when user comes back to MainActivity DataService is binded again and call to fetch the data is made and the list is updated. So if user is somewhere at item no. 205 he will be returned back to the start item.
Is there a way to hold the data or the state of MainActivity when user comes back to it that its not refreshed ?
Service is bonded like this
#Override
protected void onStart() {
super.onStart();
bind(DataService.class);
}
#Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
private void bind(final Class... toBind) {
for (final Class clazz : toBind) {
bindService(new Intent(this, clazz), connection, BIND_AUTO_CREATE);
}
}
private ServiceConnection connection = new ServiceConnection() {
#Override
public void onServiceConnected(final ComponentName name, final IBinder service) {
if (service instanceof DataService.LocalBinder) {
dataService = ((DataService.LocalBinder) service).getInstance();
dataService.readData();
}
}
#Override
public void onServiceDisconnected(final ComponentName name) {
// Empty By Default
}
};
You can achieve this by binding the service in onCreate() instead of onStart().
You should also have a look at the symmetry of your life cycle. Currently you are binding in start and unbinding in destroy. If you bind in onStart you should probably unbind in onStop. When you move the binding to onCreate you can keep the unbinding in onDestroy
Explanation: onCreate() is called when the activity is created first. onStart() is called every time your activity becomes visible.
Managed to solve it like this
#Override
public boolean onSupportNavigateUp() {
onBackPressed(); // one inherited from android.support.v4.app.FragmentActivity
return false;
}
Doing it this way seamed to do the trick. I would return to the place where I left from and there is no need to load any data since everything is already there
Related
Like I said in the title, I want to send data from an activity to a fragment, but the fragment needs to use the data received when it is created, not after a button is pressed like usual. The activity that puts the values in the bundle is the one before the activity that calls the fragment, so that the bundle is already filled when the fragment is called.
From my research the best way to do this is using a bundle, but when I do String data = bundle.getString("value"), I get a null pointer exception, meaning the bundle is empty, but I already checked and the values are there. How do I fix this?
Better to use Eventbus for passing data activity to fragment, which is simple and concise.
1- Register/unregister to an event bus
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
2- Define an event class
public class Message{
public final String message;
public Message(String message){
this.message = message;
}
}
3- Post this event in anywhere in your application
EventBus.getDefault().post(new Message("hello world"));
4- Subscribe to that event to receive it in your Fragment
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(Message event){
mytextview.setText(event.message);
}
More details:
https://github.com/greenrobot/EventBus
How to pass data between fragments
https://androidwave.com/fragment-communication-using-eventbus/
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.
I have the following activity path:
Main -> A0 -> A1 -> A2 -> B
or
Main -> B
On Main, the user can select to either show something on B, or to create something new with the A series.
After completing the A series, it goes to B.
When the user gets to B via the A route, I want the back button to go to Main. But when the user is in the A series, I want the back button to go to the previous A (or Main from the first A).
I tried using FLAG_ACTIVITY_NO_HISTORY when I create the intents, but that just makes everything go back to Main.
Is there a way to mark the activities for removal once I hit a threshold?
You could add a BroadcastReceiver in all activities you want to close (A0, A1, A2):
public class MyActivity extends Activity {
private FinishReceiver finishReceiver;
private static final String ACTION_FINISH =
"com.mypackage.MyActivity.ACTION_FINISH";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finishReceiver= new FinishReceiver();
registerReceiver(finishReceiver, new IntentFilter(ACTION_FINISH));
}
#Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(finishReceiver);
}
private final class FinishReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_FINISH))
finish();
}
}
}
To close these activities trigger the following broadcast from Activity B.
sendBroadcast(new Intent(ACTION_FINISH));
Here is the github example project for the same.
Define the logic in this very way
In Series of Activity A define the logic in this very way
public static Activity A=null;
//in onCreate();
A=this;
//Same way in B
public static Activity B=null;
B=this
now wherever you want the back condition to be checked override the OnbackPressed.
#Override
public void onBackPressed()
{
if(Activity_A.A!=null)
{
finish();
return;
}
if(Activity_B.B!=null){
//your condition
finish();
}
}
You can fire a new intent for Main activity with SINGLE_TOP and CLEAR_TOP flags in onBackPressed of B. So no matter how you landed at B, pressing back takes you to Main with all history cleared. No need to mess with A series activities.
I have a service that is, among other things, downloading images from internet. When this is done I want to show this image in a custom Activity that has a dialog theme. But I only want to use this pop up if the app is running, otherwise I just want to create a notification.
But I get an exception when I try to start an activity from my service and i feel that maybe this isn't the right way to do it?
It says:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
So my question is if this is the right way to do this by setting that flag or how should I get my downloaded image from my service to an activity. Can I in some way tell an activity to start a new activity from my service class?
I think using Broadcast Receiver is better option for you.
Add Below Method in Service and call this method when image Download Complete.
private void updateMyActivity(Context context) {
if(MainActivity.activityStatusFlag){
//update the activity if activityStatusFlag=true;
Intent intent = new Intent("mUpdateActivity");
context.sendBroadcast(intent);
}else{
//display notification if activityStatusFlag=false;
}
}
In Activity Add Following Code.
public class MainActivity extends Activity{
public static boolean activityStatusFlag= false;
//define this variable to check if activity is running or not.
#Override
protected void onResume() {
super.onResume();
activityStatusFlag = true;
this.getApplicationContext().
registerReceiver(mMessageReceiver,new IntentFilter("mUpdateActivity"));
}
#Override
protected void onPause() {
super.onPause();
activityStatusFlag = false;
this.getApplicationContext().unregisterReceiver(mMessageReceiver);
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//Display Popup or update Activity
}
};
}
I have 1 activity and 2 fragments. Both fragments use a custom AsyncTaskLoader to get some data from a webservice and as i'm using a Loader it should keep the data across activity and fragment re-creations. Both fragments override the onActivityCreated method and calls getLoaderManager().initLoader(0, null, this) which either creates a new or reuses an existing loader.
When the activity is first created, it adds Fragment #1 in a FrameLayout by default, loads the data, internally calls LoaderCallbacks.onLoadFinished() method and displays the result. I have a button which replaces Fragment #1 with Fragment #2 on click and Fragment #1 is pushed to the fragment-backstack. When the user hits the BACK key, it switches back to Fragment #1.
onActivityCreated gets called again on Fragment #1 and then obviously calls iniLoader() again. This time the data already exists in the loader and i expect it to automatically call the LoaderCallbacks.onLoadFinished method again, because it already has data available, as described here: http://goo.gl/EGFJk
Ensures a loader is initialized and active. If the loader doesn't already exist, one is created and (if the activity/fragment is currently started) starts the loader. Otherwise the last created loader is re-used.
In either case, the given callback is associated with the loader, and will be called as the loader state changes. If at the point of call the caller is in its started state, and the requested loader already exists and has generated its data, then callback onLoadFinished(Loader, D) will be called immediately (inside of this function), so you must be prepared for this to happen.
But the method is never called even if the loader exists and has generated data ready to deliver.
Edit #1
The problem from a users perspective:
User starts activity and sees fragment1 with some data
User clicks something which changes the first fragment to another, with different
data
User hits the BACK key
User is now looking at fragment1 again, but there's no data. (which means i need to get it from the webservice again - and i'd like to avoid that if possible)
Here is my activity:
public class FragmentTestsActivity extends Activity implements OnClickListener {
private Button btn1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn1 = (Button) findViewById(R.id.btn1);
btn1.setOnClickListener(this);
Fragment newFragment = new Fragment1();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.fragmentContainer, newFragment).commit();
}
#Override
public void onClick(View view) {
int id = view.getId();
if (id == R.id.btn1) {
showNewFragment();
}
}
public void showNewFragment() {
// Instantiate a new fragment.
Fragment2 newFragment = new Fragment2();
// Add the fragment to the activity, pushing this transaction
// on to the back stack.
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.fragmentContainer, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
}
My Fragment #1:
public class Fragment1 extends Fragment implements LoaderCallbacks<String> {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LoaderManager.enableDebugLogging(true);
getLoaderManager().initLoader(0, null, this);
}
private static class TestLoader extends AsyncTaskLoader<String> {
String result;
public TestLoader(Context context) {
super(context);
}
#Override
public String loadInBackground() {
// Some long-running call to a webservice - replaced with a simple string to test with
return "FirstTimeData";
}
#Override
public void deliverResult(String data) {
result = data;
if (isStarted()) {
super.deliverResult(data);
}
}
#Override
protected void onStartLoading() {
if (result != null) {
deliverResult(result);
}
if (takeContentChanged() || result == null) {
forceLoad();
}
}
#Override
protected void onStopLoading() {
cancelLoad();
}
}
#Override
public Loader<String> onCreateLoader(int id, Bundle args) {
return new TestLoader(getActivity());
}
#Override
public void onLoadFinished(Loader<String> loader, String result) {
Log.d("Fragment1", "onLoadFinished: " + result);
}
#Override
public void onLoaderReset(Loader<String> loader) {
// TODO Auto-generated method stub
}
}
Anyone know a solution to this or what i'm doing wrong here? Any help is greatly appreciated.
The correct answer, for me at least, was to move the entire Loader initialisation from onViewCreated or onActivityCreated to onStart.
After that it works fine!
From my point of view the onLoadFinished will only be called the first time cause the load has already finished, it finishes just once for both fragments. What you could do is to store the result in a property in the activity and check for it in the second fragment creation.
Update: After further investigation I found my original answer to be wrong. restartLoader will also destroy the loader if it already exists.
Nevertheless I solved my own problem. I create a standard CursorLoader in onCreateLoader and swap the cursor of my CursorAdapter in onLoadFinished. I needed to call initLoader after initializing the CursorAdapter. Now the data is still there when the fragment is returned from the backstack.
Original answer: I found two possible ways to solve this issue.
Apparently when initLoader(int id, Bundle args, LoaderCallbacks<D> callback) is called for the second time in onActivityCreated(Bundle savedInstanceState) after the Fragment is returned from the backstack, the method onLoadFinished(Loader<String> loader, String result) is never called (as you described).
However, a call to restartLoader(int id, Bundle args, LoaderCallbacks<D> callback) after or instead of initLoader will finally cause onLoadFinished to be called. To improve performance, I use a boolean argument to determine whether the Fragment is a new instance or not. Then I call restartLoader only if the Fragment is returned from the backstack.
As far as I can tell, old data persists in the loader and is not reloaded, but I'm not sure. The second possibility (especially when not using the backstack but instead creating a new Fragment instance in a transaction) is to call destroyLoader(int id) before the Fragment goes away (e.g in onPause).
I already had that issue, I can't really explain why that bug but I know that line :
getLoaderManager().initLoader(0, null, this);
don't work in my code, So you can changed it for that :
LoaderManager lm = getLoaderManager();
lm.initLoader(LOADER_ID, null, this);
The code start the methods :
onCreateLoader
you can try...