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.
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");
Hello how do you finish Main activity
Assume that there are 3 Activities and 1 Fragment
LoginActivity , MainActivity, infoFrgMent, ChangePwdActivity.
The scenario is when I loggedin in LoginActivitythen MainActivity will show up LoginActivity will finish() then i will go to my info which is 'infoFrgMent' then i want to change my password after i changed my password.
LoginActivity will shows up Again to relogin but whenever i try to press back MainActivity shows up and didn't finished.
You need to remove previous activities form stack
setFlags to intent from MainActivity to LoginActivity
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
refer : https://developer.android.com/guide/components/activities/tasks-and-back-stack.html
What you need is to add the Intent.FLAG_CLEAR_TOP. This flag makes sure that all activities above the targeted activity in the stack are finished and that one is shown.
Another thing that you need is the SINGLE_TOP flag. With this one you prevent Android from creating a new activity if there is one already created in the stack.
Just be wary that if the activity was already created, the intent with these flags will be delivered in the method called onNewIntent(intent) (you need to overload it to handle it) in the target activity.
Then in onNewIntent you have a method called restart or something that will call finish() and launch a new intent toward itself, or have a repopulate() method that will set the new data. I prefer the second approach, it is less expensive and you can always extract the onCreate logic into a separate method that you can call for populate.
To finish another Activity you have to create a static method to finish this Like here:
MainActivity.java
private static MainActivity activity;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
activity = this;
}
public static void finishThis()
{
try
{
if (activity != null)
{
activity.finish();
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
And call it like this:
AnotherActivity.java
MainActivity.finishThis();
That's it
I have a class extending ParseQUeryAdapter so I can use the notifyDataSetChanged feature. The adapter class is called mainAdapter.
Here's my notifyDataSetCHanged method in the mainAdapter:
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
MainActivity mainActivity = new MainActivity();
mainActivity.getItems();
}
Here's my getItems() method in MainActivity:
public void getItems(){
if(adapter == null){
Toast.makeText(MainActivity.this, "null", Toast.LENGTH_SHORT).show();
}
}
The app crashes on loading. As you can see, I planted an if so that I can see if adapter was null. But it still crashes.
According to the debugger, it says in green after getting to the if line, "adapter:null". However, I have this in onCreate():
adapter = new mainAdapter(this);
And I declared it:
mainAdapter adapter
Is there a method I can put in that will solve my issue? Although I am implementing the class, why is it still null? I clearly stated that adapter = new mainAdapter()
You should never instantiate your activity classes with new; they should be interacted with using startActivity() and related APIs.
An activity created with new won't be registered with the ActivityManager, won't show up on the screen, and won't have any of it's lifecycle callbacks called.
Since your MainActivity instance's onCreate() method has not been run, adapter has not been created.
In your case, it seems like you would want your ParseQueryAdaptor subclass to have a reference to your activity in some way, so that it can access the right one.
If you want to reference an existing activity then you have to pass the activity object to where you want to use it (or use getContext or getActivity when your in a class that has that available).
One of the things you can do is create a method that passes the MainActivity object into your ParseQUeryAdapter. Then when you are calling stuff in your adapter do: activityObject.whatevermethodyouwantocallontheactiviy() be shure to error check the activity object first though.
IE:
private MainActivity activity;
public void setup(MainActivity activity){ this.activity = activity;}
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
if(activity != null){
activity.getItems();
}
}
It is surprisingly difficult to get the current fragment when using either of the pager adapters. With the FragmentPagerAdapter, however, you can look for a fragment with the tag "android:switcher:" + viewId + ":" + id.
Unfortunately, there does not seem to be a standard tag for the FragmentStatePagerAdapter. A related question provided a couple answers which suggested manually keeping a cache of the fragments, which were noted as being inadequate when doing a rotation: the underlying adapter stores state in a bundle and restores it when it is created, causing any simple caching solution to fail.
I found a better solution. getCurrentFragment() cannot be implemented correctly from what I can tell.
My code was previously launching a dialog and then calling back to the Activity which was stored by the dialog at onAttach. The Activity then needed to find the correct Fragment, which was problematic.
The correct solution is to first call setTargetFragment() on the new dialog fragment:
SelectProblemDialogFragment f = SelectProblemDialogFragment.newInstance(args);
f.setTargetFragment(this, 0);
f.show(getFragmentManager(), "select_problem_dialog_fragment");
and then in onAttach(), simply use that as the listener.
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try
{
mListener = (SelectProblemDialogListener) getTargetFragment();
if (mListener == null)
{
mListener = (SelectProblemDialogListener) activity;
}
} catch (ClassCastException e)
{
throw new ClassCastException("Must implement SelectProblemDialogListener");
}
}
I have searched the numerous questions that look like this one, but haven't found my answer in any of them.
I have an activity that has 3 tabs accessible through the action bar. I achieved this by adding 3 fragments that inflate a custom view I made extending the view class.
At the moment the database changes, I try to refresh the view in my tab by calling invalidate()/postinvalidate(), but this does not work. The same is true for calling onCreateView of the fragment just as many other options I considered.
When I go to another tab and go back, however, the change has been made and my view is updated as it should be.
How can I simulate the same thing that happens when changing to another tab? What does happen. I tried to look at the Fragment lifecycle (tried to call onCreateView()) to figure it out but it just doesn't want to refresh/redraw as it should.
The data is loaded properly, as the data is changed when I change to another tab.
I deleted some of the code as it is no longer relevant. I implemented Cursorloaders instead of my own Observer pattern to notify a change. This is my main activity right now.
The question is what should I do now if I want to redraw the view inside these fragments. If I apply fragmentObject.getView().invalidate() it does not work. I'm having the same problem as before, but now my Observer to notify a change in the database is properly implemented with loaders.
public class ArchitectureActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ActionBar actionbar = getActionBar();
actionbar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.Tab EditTab = actionbar.newTab().setText("Edit");
ActionBar.Tab VisualizeTab = actionbar.newTab().setText("Visualize");
ActionBar.Tab AnalyseTab = actionbar.newTab().setText("Analyse");
Fragment editFragment = new EditFragment();
Fragment visualizeFragment = new VisualizeFragment();
Fragment analyseFragment = new AnalyseFragment();
EditTab.setTabListener(new MyTabsListener(editFragment));
VisualizeTab.setTabListener(new MyTabsListener(visualizeFragment));
AnalyseTab.setTabListener(new MyTabsListener(analyseFragment));
actionbar.addTab(EditTab);
actionbar.addTab(VisualizeTab);
actionbar.addTab(AnalyseTab);
ArchitectureApplication architectureApplication = (ArchitectureApplication)getApplicationContext();
architectureApplication.initialize();
getLoaderManager().initLoader(0, null, this);
getLoaderManager().initLoader(1, null, this);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == 0){
return new CursorLoader(this, GraphProvider.NODE_URI , null, null, null, null);
} else if (id == 1){
return new CursorLoader(this, GraphProvider.ARC_URI , null, null, null, null);
}
return null;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Reloading of data, actually happens because when switching to another tab the new data shows up fine
Log.e("Data", "loaded");
}
public void onLoaderReset(Loader<Cursor> loader) {
}
}
Don't try to call onCreateView() yourself... it's a lifecycle method and should be called only by the framework.
Fragments are re-usable UI components. They have their own lifecycle, display their own view, and define their own behavior. You usually don't need to have your Activity mess around with the internal workings of a Fragment, as the Fragment's behavior should be self-contained and independent of any particular Activity.
That said, I think the best solution is to have each of your Fragments implement the LoaderManager.LoaderCallbacks<D> interface. Each Fragment will initialize a Loader (i.e. a CursorLoader if you are using a ContentProvider backed by an SQLite database), and that Loader will be in charge of (1) loading the data on a background thread, and (2) listening for content changes that are made to the data source, and delivering new data to onLoadFinished() whenever a content change occurs.
This solution is better than your current solution because it is entirely event-driven. You only need to refresh the view when data is delivered to onLoadFinished() (as opposed to having to manually check to see if the data source has been changed each time you click on a new tab).
If you are lazy and just want a quick solution, you might be able to get away with refreshing the view in your Fragment's onResume() method too.
I had a similar (although not identical) problem that I could solve in the following way:
In the fragment I added
public void invalidate() {
myView.post(new Runnable() {
#Override
public void run() {
myView.invalidate();
}
});
}
and I called this method from the activity when I wanted to refresh the view myView of the fragment. The use of post() ensures that the view is only invalidated when it is ready to be drawn.
I've found a workaround to refresh the view inside my fragment. I recreated (new CustomView) the view every time the database has been updated. After that I call setContentView(CustomView view). It looks more like a hack, but it works and nothing else that I tried does.
Although my problem was not actually solved, I gave the reward to Alex Lockwood. His advice on Loaders made my application better and it caused me to keep looking for a solution that I eventually found.
I had the same issue.
My solution was detach fragment and attach it again.
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment f = getFragment(action);
if(forceUpdate)
{
fragmentTransaction.detach(f);
fragmentTransaction.attach(f);
}
fragmentTransaction.replace(R.id.mainFragment, f);
fragmentTransaction.commit();
currentAction = action;
The fastest solution working for me:
#Override
public void onPause() {
super.onPause();
if (isRemoving() && fragmentView != null) {
((ViewGroup) fragmentView).removeAllViews();
}
}