I have an activity with 3 tabs: A, B & C. For every tab, I’ve created a fragment (-f) and presenter (-p). The problem is that all three fragments (A-f, B-f, C-f) are the same, but presenters are not. So the question is how I can avoid code duplicity? I’ve tried to create a BaseFragment and extend it from A-f, B-f, C-f, but if I’m in A-f and something happens C-f (like UI update), then I receive
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView.setVisibility(int)' on a null object reference, because C-f at this is destroyed (am I right?) I don't want to create 3 same fragments with the same layouts.
I've done something similar and I've found using Views a lot simpler and less buggy. The android fragment managers can exhibit unpredictable behavior at times when executing various transactions. Here's a quick sample of how it can work:
YourActivity extends Activity {
View a,b,c;
#Override
protected void onCreate(Bundle savedInstanceState) {
a = getLayoutInflater().inflate(R.layout.YOUR_LAYOUT_ID, null);
b = getLayoutInflater().inflate(R.layout.YOUR_LAYOUT_ID, null);
c = getLayoutInflater().inflate(R.layout.YOUR_LAYOUT_ID, null);
}
}
The activity is the presenter / controller for android, so I would go ahead and have the logic here instead of defining a presenter class for now. On each tab press you could then control which view to show. I don't see the code in which your are performing fragment transition so I cannot comment for sure if your fragment was destroyed.
Related
I am trying to enable user to go to a new Fragment when a list item is clicked. That's OK. I created an interface which allows me to handle click events from my FragmentA.java class. FragmentA is attached to my activity when activity started. my activity extends FragmentActivity.
In my activity class:
#Override
protected void onCreate(Bundle savedInsantaceState){
//...
getSupportFragmentManager().beginTransaction().replace(R.id.container, FragmentA.newInstance(param1, param2)).commit();
}
And then in FragmentA.java, i set that to my RecyclerView Adapter as click handler. I use add() method instead of replace() method to change the fragment, because i want to save the FragmentA's state (like RecyclerView position etc.) when FragmentB is attached.
private void setListeners(){
mAdapter.setOnItemClickListener(itemClickListener);
}
private ItemListAdapter.ItemClickListener itemClickListener = new ItemListAdapter.ItemClickListener() {
#Override
public void onItemClicked(View v, ItemModel item) {
FragmentManager manager =((FragmentActivity)mActivity).getSupportFragmentManager();
manager.beginTransaction().add(R.id.post_activity_layout_container, FragmentB.newInstance(item, param2, param3)).addToBackStack("comment").commit();
}
};
HERE IS THE ISSUE : In this case, FragmentA is running but invisible, while user sees FragmentB. User can reach views of FragmentA, and that cause problems. I wanna save the last state of FragmentA but user should not click on views of FragmentA from FragmentB. How to handle that issue? Is there a better practice to accomplish saving the last state?
EDIT
FragmentA contains some sorting, filtering. When i use replace() method, all filters that user set is invalidated, and also RecyclerView position became 0. Imagine that user is looking at (for example) 33. item in the list, clicks on it, FragmentB is attached, then go back to FragmentA. I want user to continue from 33. item, don't want user to try to find for where he was.
I'm not sure what you mean by "I wanna save the last state of FragmentA" exactly, but, AFAIK, the fact that you replace Fragments in a container doesn't mean they lose state. For example, you can still click on back button and this will revert the transaction, bringing the previous Fragment from the back-stack.
Edit: the effects that you observe are most probably caused by the destruction and re-creation of Fragment's View hierarchy. There are couple of approaches around it. The first one would be to store UIs state and restore it after re-initialization of the Fragment. Unfortunately, it might be tricky with RecyclerView position (you can google it). Another simpler approach (which is a hack) is to create the root View in onCreateView only once, keep a reference to it inside Fragment and return the same View on subsequent calls to onCreateView. If you decide to use the later approach, be careful because you'll be using Fragments not exactly the way there were intended to use.
Not directly related to your question, but I absolutely recommend avoiding manual Fragments management. It'll be too painful. You can use the official Navigation Component, or, alternatively, a simpler solution like FragNav library. I wrote this post about the later and it might help you.
I have an activity:
public class MainActivity extends FragmentActivity
{
private FragmentA a;
private FragmentB b;
private FragmentC c;
private HomeFragment mHomeFragment;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.home_activity_layout);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, mHomeFragment) // replace flContainer
.addToBackStack(null)
.commit();
...
}
I use some lazy initializer, and I keep my object a, b, c and mHomeFragment inside my activity object,
I have a button inside 'b' that calls the MainActivity to call 'c'
and a button inside 'c' that calls the MainActivity to call 'b'
and some other buttons to call 'a' which is not that important
Well now inside my FragmentB I have some custom views,
public CustomView(Context cnx, SettingsViewElement e, FragmentA fragment)
{
super(cnx);
inflate(cnx, R.layout.my_layout,this);
parent = fragment; // I use it later in my onCheckedChangeListener to tell him the the switch has been checked
condition = parent.getCondition();
titleTv = this.findViewById(R.id.title_tv);
switch = this.findViewById(R.id.switch);
if(condition){
switch.setChecked(true);
titleTv.setTitle("i am depressed af :(");
}
my question is :
why my switch doesn't turn checked even if my titleTv is updating the title ??
I did some tests and my OnCreated() is called each time I call replace() in my fragment transaction.
my fragment re-instantiate the views and then will add them to a linearLayout after inflating layout.
Also, if I don't keep my fragments reference in my MainActivity and then re-instantiate it every time.
here : the switch.setChecked will work ,
but the backStack will be huge and dumb
example: stack = 'b' -> 'c' -> 'b' -> 'c'
normally when the user click twice return button he'll be back to homeFragment, it doesn't need to empty the stack by itself.
and even if I override #onBackPressed() and re-instantiate the fragments each time hoping the android sdk will free the space.
I will have to do it manually each time I want to add a new fragment in my design, it will be a a wheel re-invention.
and : IT DOES NOT EXPLAIN WHY THE Switch.setChecked() doesn't work even if the condition is true and the other views are updating (textView is updating it's text)
Update : Apparently,
if the fragment is being re-used and the onCreate() is called for the
second time, (the reference for the fragment is being kept somewhere
and fragment is being attached for a second time) here the setChecked
will not take an effect, only some other view updates
meanwhile, it will only work if it's being called for the first time
the fragment is being created,
Solution : updating the views in the OnResume() method
If someone has an explanation for this, please go ahead
Update :
Apparently,
if the fragment is being re-used and the onCreate() is called for the seconde time, (the reference for the fragment is being kept somewhere and fragment is being attached for a seconde time)
here the setChecked will not take an effect, only some other view updates
meanwhile, it will only work if it's being called for the first time the fragment is being created,
Solution :
updating the views in the OnResume() methode
If someone has an explanation for this,
please go ahead
Views save and restore (some) of there state in the course of saveInstanceState/restoreInstance cycle, but unfortunatrly the checked state (and also visibility etc) are not covered by that.
So you'll have implement onSaveInstanceState etc. for your fragment and save/restore the checked state yourself.
EDIT
From your question it is not apparent what view switch actually is so I cannot give you actual code. But have a look at the implementation of CheckedTextView and check out the implementation of onSaveInstanceState() and onRestoreInstanceState(). You'll see that they save and restore the checked state (it's quite a lot of code with the SavedState class but in the end it is pretty simple). What you need to do, is to implement those two methods for your custom view (also creating your own SavedState inner class) and save/restore the checked state of your switch view.
A completely different and better approach would be to use a ViewModel for your fragment that holds the state, but that's a different story that would require lots of changes to your complete implementation.
I am having some problems to understand the differences between Activity and Fragment.
I have done an activity called "PublicarActivity" and a Fragment called "PublicarFragment".
They have exactly the same code (with some differences to work as a fragment and as an activity) so that is not a problem.
My problem is that I do not really know how to work with "onBackPressed". I know that before than calling the fragment, you should add it to the stack, but right now I would like to do something a little bit more complicated.
This is the code for my Activity's onBackPressed:
#Override
public void onBackPressed() {
if(layout_activado){
verificable.toggle();
verificar_layout.setVisibility(View.INVISIBLE);
layout_activado = false;
pulsado = false; }
else{
Intent intent_cancelar = new Intent(PublicarActivity.this, Principal_Activity.class);
startActivity(intent_cancelar);
}
}
How could I do exactly this from my fragment?
There are two things in your question to be solved to get you the answer.
First thing is confusion between Activity and Fragment. You might have encountered an statement -"Activity represents single screen" in Android. So having Activity in your application will let your user interact with various views such as buttons, lists etc. So now, let's consider an instance when you want to add such a view in your Activity which should contain some state lifecycle (like you can have list in fragment and clicking on item should lead you to detailed view in the same view) so that you can have mini-Activity in your main activity while all other components remaining at the same positions. So providing functionalities like mini-activity your Fragment is going to have some life-cycle methods which will be called during Fragment Life time. So you can use Fragment whenever you feel you want some sub-Activity in your main Activity or for any other use. You can cover your whole Activity with Fragment as we mostly do whenever we want to have Navigation-Drawer in our app.
Now that you have got clear about Fragment and Activity( I hope so) you can refer to the link provided by person named cricket which is this.
Consider the following scenario:
I have an Activity with UI elements that launches a DialogFragment when clicked
The DialogFragment has a listener interface that the Activity provides an implementation of. Say for example, the Activity is an image editor and the DialogFragment selects a contrast - the dialog would have a OnContrastChangedListener that the Activity implements
The Activity has implemented this interface to update views in its UI. Continuing the image editor example, the Activity implements the OnContrastChangedListener to update its preview view - something like this:
contrastDialog.setOnContrastChangedListener(new OnContrastChangedListener {
#Override
public void OnContrastChanged(int newContrast) {
getPreviewView().updateWithContrast(newContrast);
}
});
The orientation is changed, and everything is recreated and the listener saved and restored correctly using the methods recommended here (listener is saved in a Fragment and restored when the lifecycle is restoring state).
The problem is the listener interface now does not work. The function getPreviewView() is now returning null even though when called anywhere else in the Activity it returns the correct value
Excuse the poor terminology (my knowledge in compiling and bytecode is limited), but I can grasp what has happened. The interface has been compiled with the getPreviewView() version that returned the preview view that was destroyed on the orientation change, and this has since been released / garbage collected / is now null.
My question is, is there a way in Java to make the interface compile expecting the values / functions to change - much like the volatile keyword in C (I am expecting there isn't)? In that case, what's the best approach for getting around this type of situation? I have considered the following:
Create the DialogFragment (and its interface) in the code that is rerun when the Activity is recreated. This is fine for things like OnClickListeners for Buttons as they are definitely created. But this DialogFragment is only created when a button is pressed, so this approach means every dialog for the screen is created each time the Activity is - this seems wasteful given they may not even be run
Create all possible interfaces for the Activity every time and save them in member variables, then use these interfaces when the DialogFragments are requested to be created by the event. Same comments as above - seems wasteful creating every possible interface just in case it is run.
Keep some hacky "open dialog state" member variables in the Activity that guide the recreation of the interfaces. Hacky and creates a tie between the Activity and the DialogFragment which isn't great practice.
As you can see, all options involve recreation that is wasteful to some extent - is there a way to reuse the existing interface implementation?
EDIT: Options 1 and 2 won't work because they need a link to the existing Dialog. This is all doable but it is leaning more and more towards the hacked together option of having 'current Dialog' variables, getting the DialogFragment with FragmentManager when the activity is restarted, casting it appropriately based on the 'current Dialog' variable, recreating the listener. Is there a less messy way?
the onAttach onDetach method is good and I like using it, sometimes, when I know there will be more developers in the code I don't even cast it blindly, but I do a check like this:
if(activity instanceof MyInterface){
interface = (MyInterface) activity;
} else{
thrown new RuntimeException("Dear colleague, this fragment was meant to have the activity implementing MyInterface or else a bunch of other stuff won't work, please go back to your code and add the interface");
}
but as a different resort, you can also re-set the interface when the fragment is recreated. For example, on the activity onCreate
if(savedInstanceState != null){
mDialogFrag = getSupportFragmentManager().findFragmentByTag(MyDialogFrag.TAG);
if(mDialogFrag != null)
mDialogFrag.setListener(... the interface ...);
}
I know that's also not the best separation of objects, but the fact is that getPreviewView() NEEDS the current activity to proper operate, so you NEED to pass this reference again when everybody gets destroyed n rebuilt.
Those are just different ways of doing it.
If the activity will always have the implementation of the interface, would it be possible to set your listener in the DialogFragment's onAttach()? This will ensure that when it is destroyed and recreated, it will have the most up-to-date reference, like:
public void onAttach(Activity activity) {
super.onAttach(activity);
contrastChangedListener = (OnContrastChangedListener)activity;
}
public void onDetach() {
super.onDetach();
contrastChangedListener = null;
}
If I want to display a MapView inside my Activity I would have to extend my Class from MapActivity. If I want to display a Tabbed interface I would have to extend my Class from TabActivity. Similar is the case with some other controls which require user class to extend from a specific class.
Let's say inside my Activity I want to display both a MapView for displaying Google Map and a TabView to display some other data. I can't do it directly because Java doesn't support multiple inheritance. My question is how can I achieve this scenario? How can I display multiple controls inside my activity where each require your class to extend from a specific class. Is it possible first of all? If yes, what are the best practises to achieve this scenario?
Update I want to achieve this
I am using Map and Tab for sake of an example. I would like to know how you can tackle this scenario in general?
In this case it's simple: You can use the MapActivity within the TabActivity (it's designed to manage activities as tabs).
As general approach I always prefer to use views and nest them in an activity. I never use such things like ListActivity. They should make things easier but often look like a bad design decision to me. I never faced the fact that I had to combine two activities (expect TabActivity).
You can take a look at this question. It seems that activities never meant to be used that way. I think the situation which you describe is the reason why fragments where introduced.
You could build it via object composition. Initially I am not sure how to get the Activity started and add it to the layout, but then I found out about LocalActivityManager which allow you to embed other Activity as your view. Note that this class is deprecated since API Level 11. In any case here are the steps to embed other Activity that require extension as a View:
Create a LocalActivityManager to enable creation of Activity within Activity
Start the activity that you want to embed and get the View via getDecorView()
Add the View in your layout
The following is my test code that I tried within my Activity
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create local activity manager so that I could start my activity
LocalActivityManager localActivityManager = new LocalActivityManager(this, true);
// dispatch the onCreate from this manager
localActivityManager.dispatchCreate(savedInstanceState);
// layout to hold the activity, optionally this could be set through XML file
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
this.addContentView(layout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT));
// start the activity which is in this example is an extension of a TabActivity
Intent tabIntent = new Intent(this, DummyTabActivity.class);
Window tabWindow = localActivityManager.startActivity("tabView", tabIntent);
View tabView = tabWindow.getDecorView();
// start the activity that extends MapActivity
Intent mapIntent = new Intent(this, DummyMapView.class);
Window mapViewWindow = localActivityManager.startActivity("mapView", mapIntent);
View mapView = mapViewWindow.getDecorView();
// dispatch resume to the Activities
localActivityManager.dispatchResume();
// add to the tabView, optionally you could use other layout as well
layout.addView(tabView);
// add to the mapView, optionally you could use other layout as well
layout.addView(mapView);
}
My limited experiments show that object composition via the above method will achieve what you are trying to do. Having said that, I am not sure how common this approach is. Without your question, I wouldn't probably look for the above method, but your use case is interesting and might be applicable for future use. I will look into Fragment and see if I could do the same with it and update my answer if it is applicable.