I am writing an Android app (4.4) that uses Fragments. Each Fragment is in it's own .java file (and its own class), and each one has it's own .XML (layout) file. In the main FragmentActivity, my "getItem" routine reads the "position" argument, and creates instances of these classes as needed.
When the app starts, when Fragment 0 (zero) starts up, it runs some code in the "onCreateView." Based on what happens in that code, I need to change the UI of the Fragment 1 (buttons appear & disappear based on that logic).
However, the code RUNS with no errors, but the UI changes do not take effect. I'm thinking that perhaps I need to run my "startup" code somewhere else with a wider scope. I could be wrong.
Can anyone suggest a way for me to be able to control the UI of various layouts at startup?
Thanks!
If you can post some of your code, would be easier.
anyway if I got your problem, you need to change the UI of the fragment 1 from the fragment 0.
What you need is what is explained in the document Communicating with Other Fragments
you should do something like:
public class MyActivity extends FragmentActivity implements MyInterface{
#Override
public void changeUI(String sometext) {
Fragment1 fragment1 = (Fragment1) getFragmentManager().findFragmentByTag("tagCommittedFragment1");
fragment1.applyChange(sometext);
}
}
public class Fragment0 extends Fragment{
MyInterface mMyInterface;
public interface MyInterface {
public void changeUI(String sometext);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mMyInterface = (MyInterface) activity;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mMyInterface.changeUI("newtext");
}
}
public class Fragment1 extends Fragment{
public void applyChange(String sometext){
// do your work
}
}
You must make an interface to communicate between Fragments which would be implemented by your MainActivity:
public interface Communicator {
public void respond(String data);
}
Now you need to use this Interface to send data from FragmentA:
Communicator comm = getAcitivity(); //your activity must implement this interface
comm.respond(data);
As your MainActivity implements the above interface, it will also implement the respond() method which can be used to pass data to FragmentB:
public void respond(String data){
FragmentManager manager = getSupportFragmentManager();
FragmentB fragB = manager.findFragmentById(R.id.fragment_b);
fragB.changeData(data);
}
Now all you need to do is collect this data and make changes in FragmentB using a changeData() method:
public void changeData(String data){
textView.setText(data);
}
NOTE: As FragmentB has no use of the Interface, it should not be visible to it, therefore you can also create the interface inside FragmentA instead.
Related
I have implemented a master-detail view. With two fragments being displayed side by side on a large 10-inch screen. Fragment A Displays a list of orders. When an order is selected the details of that order are displayed in Fragment B. In fragments B after processing the order items. I want to notify Fragment A to update the UI and colour the processed order in the list of orders.
The current method that I have tried was creating an interface in Fragment B implementing the interface in Fragment A. However, this method does not seem to work as when I try and set the instance of the interface in the onAttach method the application crashes as the context is still the context of Fragment A.
#Override
public void onAttach(#NonNull Context context)
{
super.onAttach(context);
if (context instanceof OnStockAddedListener)
{
onStockAddedListener = (OnStockAddedListener) this.getActivity();
} else
{
throw new ClassCastException(context.toString());
}
}
How can i go about doing this.
Your fragments are hosted in an Activity, and that activity is what's passed to onAttach(). So your activity needs to be responsible for dispatching communication between your fragments.
So, in FragmentB, you cast your Activity to your listener interface when you're attached:
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.onStockAddedListener = (OnStockAddedListener) context;
}
And you implement the interface in your Activity:
public class MyActivity implements OnStockAddedListener {
#Override
public void onStockAdded(Stock stock) {
FragmentA fragmentA = (FragmentA) getSupportFragmentManager()
.findFragmentByTag(/* insert tag here */);
fragmentA.handleStockAdded(stock);
}
}
And you receive these messages in FragmentA:
public class FragmentA {
public void handleStockAdded(Stock stock) {
// update ui, or whatever else you need
}
}
The main thing is to not think about FragmentA talking to FragmentB, or FragmentB talking to FragmentA. Instead, FragmentA and FragmentB both talk to the Activity, and the Activity can talk (as required) with either FragmentA or FragmentB. Everything flows through the activity.
My fragment class is
public class GetFragmentManager extends FragmentActivity {
public FragmentManager getSupportFragmentMethod(){
FragmentManager s = getSupportFragmentManager();
return s;
}
}
I needed the getSupportFragmentManager method(which i can get from the FragmentActivity class) so i made this class which extends the FragmentActivity class.
My Activity code(this extends the Activity class) is
public void showFileChooser(View v){
DialogFragment a =new FireMissilesDialogFragment();
a.show(getSupportFragmentManager1(), "missiles");
}
private android.support.v4.app.FragmentManager getSupportFragmentManager1() {
android.support.v4.app.FragmentManager ab = new GetFragmentManager().getSupportFragmentMethod();
return ab;
}
The error statement coming is Activity is being destroyed.
Please can anyone find what is going wrong in here.I have spend many hours on this.Thanks everyone for your time.
Sorry for wasting all of your time.
It seems that Activity class has a method getFragmentManager(), which I knew at the time, but wasn't running correctly, as I was wrong in referencing a class (specifically the fragment class) in a code line. Also at every import on fragments I imported not the app.v4 support version but the main version.
The main activity class extends the Activity class
public class Profile extends Activity implements View.OnTouchListener{
And on the same class the open fragment method is written
public void showFileChooser(View v){
a =new FireMissilesDialogFragment();
a.show(getFragmentManager(),"text");
}
and the FireMissilesDialogFragment is as follow.
public class FireMissilesDialogFragment extends DialogFragment {
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage("dialog_fire_missiles")
.setPositiveButton("fire", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// FIRE ZE MISSILES!
}
})
.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
// Create the AlertDialog object and return it
return builder.create();
}
}
The things which changed are that for every app.v4 support version import i didn't import the v4 version but the regular version. Even if the dialog works the manifest file will show the error "java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity"
Anyways thanks everyone for your time. I will upvote the two answers as they have been useful in coming at this point.
you can not instantiate an Activity like you instantiated new GetFragmentManager() , pass an already instantiated activity to the method.
For example your main activity (the activity currently on screen) extends fragment manager, then inside that you call this.getSupportFragmentManager()
First of all, if you want to use activity for starting a fragment you first need to pass the onCreate threshold, then you create the fragment:
public class MyActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle saveInstanceState){
super.oncCreate(saveInstanceState);
//create your file chooser, etc.
DialogFragment a =new FireMissilesDialogFragment();
a.show(getSupportFragmentManager(), "missiles");
//getSupportManager exists in the activity
}
}
You can also create it in onResume, onStart, whenever you feel like.
Edit
For appcompat you can look at the following to understand the issue:
Update your style resources
For relevant stack posts:
You need to use a Theme.AppCompat theme (or descendant) with this activity
As you can understand you need to define the theme in your activity or
in your application:
android:theme="#style/Theme.AppCompat" >
just extend AppCompatActivity instead of Activity and then you can use
FireMissilesDialogFragment a =new FireMissilesDialogFragment();
a.show(getSupportFragmentManager1(), "missiles");
I basically have a MainActivity that has multiple tabs. Each tab is a ShowListFragment and it extends Fragment. Now, each tab contains data that I fetch from a database. I have a MOVE-button that moves data from one tab to another in each Fragment:
#Override
public void onClick(DialogInterface dialog, int listIndex) {
database.add(listIndex,object);
database.remove(listIndex,object);
}
The fragment does not update directly, but after a few swipes in between the tabs (3 exactly). How do I force the Fragment to update instantaneous after I clicked the button? I don't want to manage it through onPageSelected in the ViewPager, since it does not update the fragment I'm currently on, but after I've swiped to the next fragment. And also I don't want to update the data after each swipe.
I know that I maybe need to use some kind of observer pattern like this: How do I make a Class extend Observable when it has extended another class too?
But still, I'm still not sure how to update the fragment directly, and how to apply the observer/event pattern in my application.
Updated Answer:
With Android architecture components, doing this is much simpler.
The recommended pattern is using a ViewModel with LiveData members. Your fragments will register observers on the LiveData members which will automatically be coordinated with lifecycle events, e.g. unregistering in onDestroy() etc. https://developer.android.com/topic/libraries/architecture/livedata
When using the Navigation component, you can pass data when navigating to a fragment: https://developer.android.com/guide/navigation/navigation-pass-data
You can also return data from the navigated fragment: https://developer.android.com/guide/navigation/navigation-programmatic
Old answer (superseded by Architecture Components):
Since the fragments can access the activity easily enough with getActivity(), I would make the activity be the central hub for dispatching updates.
It sounds like you already have the persistence part handled with the database and all you need is some update events. So here goes:
Define a listener interface. I usually do this as an inner interface within the activity:
public interface DataUpdateListener {
void onDataUpdate();
}
Add a data structure to your activity to keep track of listeners:
private List<DataUpdateListener> mListeners;
Don't forget to initialize in the constructor:
mListeners = new ArrayList<>();
Add the register/unregister methods to the activity:
public synchronized void registerDataUpdateListener(DataUpdateListener listener) {
mListeners.add(listener);
}
public synchronized void unregisterDataUpdateListener(DataUpdateListener listener) {
mListeners.remove(listener);
}
Add the event method to your activity:
public synchronized void dataUpdated() {
for (DataUpdateListener listener : mListeners) {
listener.onDataUpdate();
}
}
Have your fragments implement DataUpdateListener:
public class MyFragment extends Fragment implements DataUpdateListener {
and implement the method
#Override
public void onDataUpdate() {
// put your UI update logic here
}
Override onAttach() and onDestroy() in the fragments to register/unregister:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((MainActivity) activity).registerDataUpdateListener(this);
}
#Override
public void onDestroy() {
super.onDestroy();
((MainActivity) getActivity()).unregisterDataUpdateListener(this);
}
Fire the event in your fragment's UI update event:
#Override
public void onClick(DialogInterface dialog, int listIndex) {
database.add(listIndex,object);
database.remove(listIndex,object);
((MainActivity) getActivity()).dataUpdated();
}
I have my app working with fragment called into the main activity. Everything works great but i would like to be able to control the menu above the fragment layout.
So lets say you navigate to fragment blog posts i would like to the text view i set in the menu above the fragment to update or hide a button or show a button with set visibility.
I have no clue how to control the things who are not in the current fragment.
How should i go about this.
Thanks
Define an interface in your fragment and have your Activity implement that interface.
public interface MyInterface {
// add methods here
}
In onAttach, get the interface:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof MyInterface) {
mMyInterface = (MyInterface) activity;
} else {
throw new ClassCastException(activity + " must implement interface MyInterface");
}
}
#Override
public void onDetach() {
mMyInterface = null;
super.onDetach();
}
Then simply call mMyInterface methods as needed.
I have a Mortar application, with a MortarActivityScope as the first child under the root scope. The MortarActivityScope has an ActivityScope which #Provides an activity for injected classes:
#Module(addsTo = ApplicationModule.class, injects = {Foo.class, SomePresenter.class, AnotherPresenter.class})
public class ActivityModule {
private final Activity activity;
public ActivityModule(Activity activity) {
this.activity = activity;
}
#Provides Activity provideActivity() {
return activity;
}
}
public class Foo {
private final Activity activity;
#Inject(Activity activity) {
this.activity = activity;
}
public void doSomethingWithActivity() {
// do stuff with activity: findViewById(), getWindow(), mess with action bar etc.
}
}
This is fine until an orientation change happens. In the Mortar sample project, the Activity scope is not destroyed on orientation changes. This is presumably to allow #Singleton presenters, screens etc. to persist across orientation changes. You can see this in the onDestroy() method in the sample project's main activity:
#Override protected void onDestroy() {
super.onDestroy();
actionBarOwner.dropView(this);
// activityScope may be null in case isWrongInstance() returned true in onCreate()
if (isFinishing() && activityScope != null) {
MortarScope parentScope = Mortar.getScope(getApplication());
parentScope.destroyChild(activityScope);
activityScope = null;
}
}
}
However, doing it this way means that the old ObjectGraph persists across orientation changes. I have observed that Mortar.requireActivityScope does not replace the module from the old activity scope with the new module provided by the new Blueprint. Instead, the object graph retains a reference to the previous module, including the destroyed Activity.
public class MyActivity extends Activity implements Blueprint {
#Inject foo;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MortarScope parentScope = Mortar.getScope(getApplication());
activityScope = Mortar.requireActivityScope(parentScope, this);
Mortar.inject(this, this);
foo.doSomethingWithActivity(); //fails, because activity injected by object graph is destroyed
}
#Override
public String getMortarScopeName() {
return getClass().getName();
}
#Override
public Object getDaggerModule() {
return new ActivityModule(this);
}
}
The Mortar sample activity seems to get around this by not including a #Provides Activity method in the main module. But shouldn't the MortarActivityScope be able to inject an Activity? What's the preferred way to do this, without losing all of your singleton objects (Presenter objects, etc.) on orientation change?
Don't allow anyone to inject the Activity, that can't be made safe. Instead inject a presenter that is tied to the Activity.
How to handle onActivityResult() with Mortar includes an example of an Activity owning a presenter. Other parts of your app, including other presenters, can then inject that one and ask it to do whatever it is they need done that requires dealing with the activity.
And there is no need to tie all activity-specific work into a single activity presenter. Our activity has several presenters that broker its services to the rest of the app.