After reading Headfirst design patterns I want to know the simplest way to implement an observer design pattern in my app.
The context:
In the MainActivity of the app, after users input in EditTextView and click the button, an URL will be generated and sent to other activities for displaying.
(also switches to other activity 1)
I want to make my MainACtivity as the Subject(Observable) and Activity1 and Activity 2 as my Observers.
Instead of using the built-in observer interface, I tried to use self-defined Observer and subject interfaces to implement this pattern(like how they did it in the book).
But this way includes creating a Subject instance in the Observer class, which means I will create a MainActivity instance in my other Observer classes, I am not sure if it works, could anyone tell me how to do it properly?
(Or I just couldn't use an activity as a subject?)
You cannot observe objects in different activities. All you can do is pass the data between activities using Intents.
In Android, the observer pattern is implemented by using the class ViewModel and the class LiveData / StateFlow. If you want to have 3 different screens that observe a single object for changes. You need to have 3 Fragments that share the same Activity and the same ViewModel.
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
The LiveData is here your Subject that you update indirectly through the selected variable. The selected variable can be changed by calling select() function from a Fragment. In each Fragment, you have to create the ViewModel and observe the LiveData.
public class ListFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
model.getSelected().observe(getViewLifecycleOwner(), item -> {
// Update the UI.
});
}
}
You can read more about the example above here: https://developer.android.com/topic/libraries/architecture/viewmodel#java
This solution is also preferable because the ViewModel survives the configuration changes and can restore the state of your activity even after the activity is destroyed.
Moreover, you shouldn't keep any references of one activity in another activity or in a ViewModel, as it can lead to memory leaks.
You can try to create your own observer pattern between objects in the same activity or ViewModel if you find a use-case for it, but in most cases LiveData and StateFlow should be enough for updating your UI.
Any specific Activity instance is an ephemeral view holder. I say ephemeral because Android destroys and recreates it for any configuration change like a screen rotation. This is why it is not trivial to pass references between different Activities. It also causes memory leaks to pass around Activity instances. It is not viable to use an Activity as an observable.
What you can do is have your MainActivity publish changes to some repository class, and have that repository class expose observables that other Activities can view. In such a case, the repository must have application scope. It can be lazily created in your Application class and retrieved from the Application. If you're using a dependency injection framework, the repository would be an application-scoped singleton.
The common pattern for an Activity or Fragment to interact with a repository is through a ViewModel class.
Related
I just saw that onActivityCreated() is going to be deprecated in future. I try to implement LifecycleOwner and LifecycleObserver pattern but I'm not quite sure about what I'm doing here.
I'm using NavigationComponent, which meens :
I have a MainActivity
I have a MainFragment, instanciated as the home fragment
I have multiple fragments that can be accessed from this home fragment
For some reasons I need to know when activity is created from all of these fragments (MainFragment and sub fragments)
From what I've seen until now, I need to :
In the MainActivity, getLifecycle().addObserver(new MainFragment()). And do this for all sub fragments (which is verbose for nothing)
In fragments, implements LifecycleObserver and
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
private void onCreateEvent() {
Timber.i("%s MainActivity created", TAG);
}
This seems to work well, but I have some questions :
The syntax addObserver(new MainFragment() disturbs me. It looks like we are creating a new fragment instance, while the fragment is normally instantiated with the navigation defined in the navGraph.
As I said before, if I have my MainFragment with 10 sub fragments, I'll have to declare 11 observers ? Weird
Do I have to clear these observers at some point in the activity lifecycle ?
What is the proper way to implement it ?
EDIT 1:
To answer the question why I need to know when the activity is created :
I need this because I need to access my MainActivity viewmodel (new ViewModelProvider(requireActivity()).get(ViewModel.class). To call requireActivity() or getActivity() I need to know when the activity is created (was easy with onActivityCreated()).
Databinding is implemented with my MainActivity and this viewmodel. The layout of this activity is hosting a loader to show when network requests are performed.
I can perform requests from the MainFragment and from the sub fragments. When I perform a request from one of these fragments I need to enable this loader view, and when I got datas back I need to hide this loader.
And yes, all these fragments are in the graph
You have never needed to wait for onActivityCreated() to call requireActivity() or getActivity() - those are both available as soon as the Fragment is attached to the FragmentManager and hence can be used in onAttach(), onCreate(), onCreateView(), onViewCreated() all before onActivityCreated() is called.
This is one of the reasons why onActivityCreated() was deprecated - it actually has nothing to do with the activity becoming available to the Fragment, nor does it have anything to do with the activity finishing its onCreate() (it, in fact, can be called multiple times - every time the Fragment's view is created, not just once after the first time the Activity finishes onCreate()).
As per the deprecation notice:
use onViewCreated(View, Bundle) for code touching the Fragment's view and onCreate(Bundle) for other initialization.
Those are the recommended replacements, depending on whether the code you had in onActivityCreated() was accessing the Fragment's views or not.
Once you realize that requireActivity() can be called in onAttach(), etc., the rest of the deprecation notice makes more sense:
To get a callback specifically when a Fragment activity's Activity.onCreate(Bundle) is called, register a LifecycleObserver on the Activity's Lifecycle in onAttach(Context), removing it when it receives the Lifecycle.State.CREATED callback.
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
// Register a LifecycleObserver on the Activity's Lifecycle in onAttach()
requireActivity().getLifecycle().addObserver(this);
}
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
private void onCreateEvent() {
// Remove the LifecycleObserver once you get a callback to ON_CREATE
requireActivity().getLifecycle().removeObserver(this);
// Then do your logic that specifically needs to wait for the Activity
// to be created
Timber.i("%s MainActivity created", TAG);
}
But, as mentioned above, this is not what you should be doing if you are trying to access a ViewModel at the activity level.
I have a TextView in my MainActivity and a Button which is in a Fragment attached to the MainActivity. When I click the Button I want the TextView to say, "Button Clicked".
Is this possible?
I know two Fragments attached to the same Activity can easily communicate with each other and send data to each other. But can an object in the Fragment send data to an object in an Activity
Is it better programming to make the TextView its own Fragment and attach it to the Activity? Then I can simply have the two fragments send data to each other.
Sorry if this isn't a proper type of question for StackOverflow. I am new to Fragments and have not been able to find a clear explanation on this issue.
Thanks in advance!
The currently accepted answer (to use a static method in the Activity) is both odd and arguably "wrong".
The use of the static method is odd because there's just no need for it to be static.
It's wrong because the Fragment must have knowledge of the particular Activity in which it is hosted. This is "tight coupling", and also makes it so that the fragment is not re-usable.
There are two common solutions to this issue:
Create an interface containing the methods in the Activity that can be called by the fragment. Implement that interface in the Activity (all Activities that use the fragment), and then in the Fragment, use getActivity() to get the Activity, and cast it to the interface. In this pattern, one also typically checks (using 'instanceof') whether the Activity implements the interface, and throws a RuntimeException if it does not.
Use an Event Bus (e.g. Square's Otto, GreenRobot's EventBus) to communicate between the Fragment and it's parent Activity. I feel
that this is the cleanest solution, and completely abstracts the
Fragment from it's Activity.
You can create a static method inside your Activity which will have the TextView inside it. And when you need updatation just call it from fragment.
something like:
In Activity:
public static void updateText(){
//your textview
text.setText("Button Clicked");
}
Just call it when you will click on the Button from fragment.
something like:
From Fragment:
//Inside Button click listener
MainActivity.updateText();
Not tested, but hope this approach will work.
Have you tried the getActivity() method to retrieve a reference to the parent activity and use a method to set the data, something like:
// public final Activity getActivity ()
MyActivity activity = (MyActivity) getActivity();
activity.setText("...");
I may be wrong but I would try that.
I have set up 1 Main Activity (that extends Activity class) and 2 Fragments (that extends the fragment class).
I've tried setting up an Interface, which is implemented by the 2 fragments. And each fragment implements the particular function from the interface like this:
public stopMusic()
{
mediaplay.release(); // here, the mediaplay object belongs to only the respective fragment
}
Now, I know doesn't work, because the MediaPlayer object is not common to both fragments, i.e it's not being shared among them.
I'm looking to release the mediaplayer object streaming a file in Fragment1.java, if I hit a button from another fragment, like Fragment2java. Likewise, to release the mediaplayer object streaming a file in Fragment2.java, if I hit a button from Fragment1.java.
How can I make this happen? An example code would really help.
Let's say fragment A is the controlling fragment and fragment B is the media player. All communication should be done via the parent Activity. So in fragment B you create 'public interface(s)' which the parent activity implements, then the parent Activity should call the method(s) in fragment B.
Also depending on what you are really doing with the media player or whatever, does that belong in the background as opposed to fragment B?
Note: fragments should be loosely coupled and never communicate from fragment to fragment, always communicate via the parent Activity.
Hope this helps.
I have an app that has a top menu as shown in the fig below. This is almost constantly used in all activities. For layout I have defined it in a single xml file and use <include>to include it in every layout file.I want to know if there is a way in java coding to avoid declaring all the imageButtons, and then using findViewById and onclick events for them , in each activity. The top four icons will act as menu and be available in all layouts and activities
First of all, what you are trying to achieve is against the android standards and this could affect the user experience for Android users. Instead, add the menu items on the action bar.
Anyway, you can achieve what you are looking for by defining a base class (named it like ActivityBase) and do all the initializations and listeners registrations on it. Then, extend from that base class. Bear in mind that each activity will have its own instance of the views of the base class and the state will differ from activity to another.
Although you have accepted an answer I disagree that the Application class should be used to host global methods. It can be used for maintaining global state but that's a different matter.
As others have said, use inheritance by creating a base Activity class then extend that for all of your other Activities. To make things easier, however, you can define the onClick method to be used for each of your buttons in the layout file itself by using (for example)...
android:onClick="myOnClickMethod"
As long as the base Activity defines a public method called myOnClickMethod (or whatever method name you choose) with a void return and which accepts a View parameter it will work without the need to implement View.OnClickListener and without having to set the listener in Java code. Example
public class MyBaseActivity extends Activity {
public void myOnClickMethod(View v) {
// Get the resource id of v and use switch / case to perform action needed
}
}
That's all you need.
Write it only in the first activity. Extend it to the other activities instead of extending with android.app.Activity.
Eg:
public class SecondActivity extends MainActivity{
}
Put that method in MyApplication class which extends Application. So, that it can be accessible by multiple activities.
I receive an exception when trying to show a DialogFragment from within the onLoadFinished method of a Fragment that implements the LoaderCallbacks interface. Basically I am using the LoaderCallbacks to get some data from a rest service and then on the onLoadFinished I am trying to show a custom DialogFragment that contains a ListVeiw to allow the user to make a selection. Everything works great except when I try to launch the dialog from within the onLoadFinished. How can I accomplish this..and is this the correct approach to the problem.
Here is an example of what I am trying to do:
public class EventFragment extends Fragment implements LoaderCallbacks<someresponse> {
#Override
public void onLoadFinished(Loader<someresponse> arg0, someresponse data) {
//an exception is generated when trying to launch a dialog fragment from
//within the onLoadFinished
FragmentManager manager = getFragmentManager();
ListViewDialogFragment dialog = ListViewDialogFragment.newInstance(data);
dialog.show(manager, "event_list_dialog");
}
}
Thanks!
So after some research, I have determined that my approach was incorrect. You should never try to launch a new fragment from within the onLoadFinished method of an LoaderCallbacks async task. The framework tries to keep you from doing this because the state of the currently running fragment or activity, that implements the LoaderCallbacks is indeterminate and therefore not guaranteed to be there when the async task finishes.
Additionally trying to separate a processing dialog state and data display state into two separate fragments is a bit counter to the MVC design pattern that the android framework supports. That said, my new approach consisted of dynamically changing the view for the fragment implenting LoaderCallbacks to hide or show a specific linear layout, one for the process indicator and one for the display of the data. This approach left me modifying an existing fragment instead of launching a new one which worked out great.
Here is a link to the discussion that finally gave me clarity.
https://groups.google.com/forum/#!topic/android-developers/dXZZjhRjkMk/discussion
The approach you define is quite good, except one case - what will happen if your activity had already gone from the screen, when loading operation got finished? How it will show dialog in this case?
So generally, I would appreciate if you'll tell which exactly exception did you get.
However, as a general approach, it can be useful to check is the activity holding the fragment is still on top or even was it finished or not.
Even better - you should consider cancelling all background operations when activity/fragment is destroying, in this case you'll have no problems with showing dialogs.
Good luck!