I created an application using MVP pattern, I found this tutorial link and decided to implement it in my application in order for the fragments to communicate with their activities. I moved the implementation of the Eventbus to the correspond activity presenter and fragment presenter in order to still use the MVP pattern. Now I'm facing a new problem, one of my fragments need to change two things in the activity parameters (toolbar related and ImageView drawable). Can I somehow differentiate which callback is from in the accept function?
RxBus class
public final class RxBus {
private static SparseArray<PublishSubject<Object>> sSubjectMap = new SparseArray<>();
private static Map<Object, CompositeDisposable> sSubscriptionsMap = new HashMap<>();
public static final int CHANGE_APP_BAR_LAYOUT = 0;
public static final int CHANGE_POSTER_IMAGE = 1;
#IntDef({CHANGE_APP_BAR_LAYOUT, CHANGE_POSTER_IMAGE})
#interface Subject {
}
private RxBus() {
// hidden constructor
}
/**
* Get the subject or create it if it's not already in memory.
*/
#NonNull
private static PublishSubject<Object> getSubject(#Subject int subjectCode) {
PublishSubject<Object> subject = sSubjectMap.get(subjectCode);
if (subject == null) {
subject = PublishSubject.create();
subject.subscribeOn(AndroidSchedulers.mainThread());
sSubjectMap.put(subjectCode, subject);
}
return subject;
}
/**
* Get the CompositeDisposable or create it if it's not already in memory.
*/
#NonNull
private static CompositeDisposable getCompositeDisposable(#NonNull Object object) {
CompositeDisposable compositeDisposable = sSubscriptionsMap.get(object);
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
sSubscriptionsMap.put(object, compositeDisposable);
}
return compositeDisposable;
}
/**
* Subscribe to the specified subject and listen for updates on that subject. Pass in an object to associate
* your registration with, so that you can unsubscribe later.
* <br/><br/>
* <b>Note:</b> Make sure to call {#link RxBus#unregister(Object)} to avoid memory leaks.
*/
public static void subscribe(#Subject int subject, #NonNull Object lifecycle, #NonNull Consumer<Object> action) {
Disposable disposable = getSubject(subject).subscribe(action);
getCompositeDisposable(lifecycle).add(disposable);
}
/**
* Unregisters this object from the bus, removing all subscriptions.
* This should be called when the object is going to go out of memory.
*/
public static void unSubscribe(#NonNull Object lifecycle) {
//We have to remove the composition from the map, because once you dispose it can't be used anymore
CompositeDisposable compositeDisposable = sSubscriptionsMap.remove(lifecycle);
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
}
/**
* Publish an object to the specified subject for all subscribers of that subject.
*/
public static void publish(#Subject int subject, #NonNull Object message) {
getSubject(subject).onNext(message);
}
}
MainPresenter class
public class MainPresenter extends BasePresenter<MainView> implements Observer<ConfigurationResponse>,Consumer<Object>
{
...
#Override
public void accept(Object o) throws Exception {
//here is the problem how can I know if I should call to changeAppBar or change Image url?
}
ClientPresenter class
public class ClientPresenter extends BasePresenter<SeriesSpecsView>
{
...
//I'm calling to those function withing the fragment when the user click on the ui
public void setPosterUrl(String posterUrl)
{
RxBus.publish(RxBus.CHANGE_POSTER_IMAGE,posterUrl);
}
public void setAppBarLayoutParams(boolean collapse)
{
RxBus.publish(RxBus.CHANGE_APP_BAR_LAYOUT,collapse);
}
}
I found a two solutions for this problem:
1) to check the object by calling instanceof function, not very effective and if I will need to send the same type of information between the two events?
2) Add another evenbus but I don't think it's logical to have separate eventbus for every event you want to have callback to your activity.
Thanks for your help
UPDATE
I encountered another problem(or at least potentially problem). I added a SwipeRefreshLayout to wrap my content(which is the framelayout, each fragment that I will have will be displayed in this container). My main reason to do it was to implement a single interface between the activity and all the fragments. Let's say you don't have a network connection I will display a message to the user to swipe down in order to try to refresh the current fragment. So far I have done this by adding SwipeRefreshLayout to each of the fragments that I have. It's basically the same code and I thought to merge all the code in one place in the activity. I would love to use the EventBus but from what I understand I would need to subscribe all the fragments to the "event" onRefresh.
How can I send the event to the appropriate fragment?
I use RxBus to transmit global events. You can also use this your way.
class RxBus {
private val busSubject: Subject<ActionEvent<out Any>> =
PublishSubject.create()
fun register( onNext:
Consumer<ActionEvent<out Any>>):Disposable{
return busSubject
.observeOn(AndroidSchedulers.mainThread())
.subscribe(onNext)
}
fun post(event: ActionEvent<out Any>) {
busSubject.onNext(event)
}
}
open class ActionEvent<T>(val action: ActionEnum
, val event: T) {
}
You can use String in place of ActionEnum, which is just an enum class
When you post something,
getRxBus()?.post(ActionEvent(ActionEnum.CHANGE_APP_BAR_LAYOUT,collapse))
When you want to subscribe,
val disposable = rxBus.subscribe(Consumer{...})
Remember to dispose the disposale on destroy.
Related
I am learning ViewModel and LiveData and, in the process, a doubt arose.
What should I do if I need to start an Activity?
Is it ok to pass the context as a parameter to the ViewModel (the context will not be stored inside the ViewModel)?
ActivityAViewModel : ViewModel() {
// ...
fun openActivityB(context: Context) {
context.startActivity(...)
}
// ...
}
ActivityA {
// ...
fun onSomethingHappened() {
viewModel.openActivityB(this)
}
// ...
}
If not, what is the most correct thing to do in that case?
I like firing Events. :D
As everyone says ViewModel should not contain Context or reference to classes that contain Context. So it is not a good idea to do startActivity from ViewModel.
What I would do is have a LiveData containing data for an event. This event will be fired from your ViewModel based on your business logic (Maybe you are showing a CountDown and at the end of it you move to the next Activity?). It is a LiveData and you can observe on it. Based on the data of this event you can start your activity.
You may want to look at SingleLiveEvent
You should call startActivity from activity, not from viewmodel. If you want to open it from viewmodel, you need to create livedata in viewmodel with some navigation parameter and observe on livedata inside the activity.
IMHO, viewmodel should know nothing about view and how it presents info to user.
/**
* Activity (as view) responsible only for gathering actions and intentions from user and
* show result state.
* View must know "What user want". View knows meaning its interface.
* Click on button 'login' means INTENTION to login somewhere.
* This intention pass to ViewModel to process it and wait some changing state from LiveData.
* For example implemented as Actions.
*/
public class LoginActivity extends AppCompatActivity {
private LoginViewModel mLoginViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLoginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
mLoginViewModel.getAction().observe(this, new Observer<Action>() {
#Override
public void onChanged(#Nullable final Action action) {
if(action != null){
handleAction(action);
}
}
});
//Emulate user intention
mLoginViewModel.userWantToLogin("0123456789", "admin");
}
private void handleAction(#NonNull final Action action) {
switch (action.getValue()){
case Action.SHOW_WELCOME:
//show Activity.
break;
case Action.SHOW_INVALID_PASSWARD_OR_LOGIN:
//show Toast
break;
}
}
}
public class LoginViewModel extends ViewModel {
//Stores actions for view.
private MutableLiveData<Action> mAction = new MutableLiveData<>();
public LiveData<Action> getAction() {
return mAction;
}
/**
* Takes intention to login from user and process it.
*
* #param password Dummy password.
* #param login Dummy login.
*/
public void userWantToLogin(String password, String login){
if(validateInfo(password, login)){
showWelcomeScreen();
}else {
showPasswordOrLoginInvalid();
}
}
/*
* Changes LiveData. Does not act directly with view.
* View can implement any way to show info
* to user (show new activity, alert or toast)
*/
private void showPasswordOrLoginInvalid() {
mAction.setValue(new Action(Action.SHOW_INVALID_PASSWARD_OR_LOGIN));
}
/*
* Changes LiveData. Does not act directly with view.
* View can implement any way to show info
* to user (show new activity, alert or toast)
*/
private void showWelcomeScreen() {
mAction.setValue(new Action(Action.SHOW_WELCOME));
}
//As example of some logic.
private boolean validateInfo(String password, String login) {
return password.equals("0123456789") && login.equals("admin");
}
}
public class Action {
public static final int SHOW_WELCOME = 0;
public static final int SHOW_INVALID_PASSWARD_OR_LOGIN = 1;
private final int mAction;
public Action(int action) {
mAction = action;
}
public int getValue() {
return mAction;
}
}
It would be a good design choice if the viewmodel knows nothing about the activities. Basically, viewmodel and activities play observable and observers roles. ViewModel, being a wrapper around your repository or business model or orchestration layer, provides the reactive style data streaming and plays observable role. It means, several activities or fragments, being observers, can listen to one view model.
So it is better to keep louse coupling, by not tightening the one particular activity to one view model but it is common convention among mobile developers that they prefer to create one view model to one activity/fragment.
If you have retrofit or okhttp or other libraries that need context, pass them context thru dagger2 or Koin DI libraries. It would be a clean architecture.
You can use an Application context which is provided by the AndroidViewModel, you should extend AndroidViewModel which is simply a ViewModel that includes an Application reference.
How do you do that? I have the object class implementing parcelable but i don't know what to do for sending the object from one fragment to another one. Help me please.
You can use the navGraph to share data between fragments.It's easy.
Sharing data between fragments is always painful, as both fragments need to define same interface description and the owner activity must bind two together.
And also need to handle the conditions like other fragment not created or not visible
But with new ViewModel, our life become easy to deal with fragment communication. All we have to do is just create a common ViewModel using the activity scope to handle the communication.
Let’s take an example where as in one fragment we need to show list of news articles , and another to show details of the selected news article.
Step1:- Create the Article model class.
public class Article {
private int articleID;
private String articleName;
private String details;
public int getArticleID() {
return articleID;
}
public void setArticleID(int articleID) {
this.articleID = articleID;
}
public String getArticleName() {
return articleName;
}
public void setArticleName(String articleName) {
this.articleName = articleName;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}
Step2:- Create a ArticleViewModel which holds the objects.
public class ArticleViewModel extends ViewModel {
private LiveData<List<Article>> articleList;
private final MutableLiveData<Article> selectedArticle = new MutableLiveData<Article>();
public MutableLiveData<Article> getSelectedArticle() {
return selectedArticle;
}
public void setSelectedArticle(Article article) {
selectedArticle.setValue(article);
}
public LiveData<List<Article>> getArticleList() {
return articleList;
}
public void loadArticles() {
// fetch articles here asynchronously
}
}
Step3:- Create a ArticleListFragment which take care of your list.
public class ArticleListFragment extends Fragment {
private SharedViewModel model;
public void onActivityCreated() {
ArticleViewModel model = ViewModelProviders.of(getActivity()).get(ArticleViewModel.class);
listItemSelector.setOnClickListener(article -> {
model.setSelectedArticle(article);
});
}
}
Step4:- Create your ArticleDetailFragment to show details of article
public class ArticleDetailFragment extends LifecycleFragment {
public void onActivityCreated() {
ArticleViewModel model = ViewModelProviders.of(getActivity()).get(ArticleViewModel.class);
model.getSelectedArticle().observe(this, { article ->
// update UI
});
}
}
If you observe, both fragments are using getActivity() while getting the ViewModelProviders. Means both fragments receive same ArticleViewModel instance, which is scoped to your parent Activity.
Its just that simple and we get more benefits like
Your Activity no need to worry about this communication
Even one fragment get destroyed, other one use the data in ViewModel.
Happy coding :)
I'm currently trying to move an application over to RxJava 2 to replace our broadcast receiver methods of passing events around. The goal here was to used Rx with a pubSub pattern in mind.
(Just a heads up, the following class which acts as an event bus was taken from an article I found online and I do believe I don't understand the working to 100% which I is probably why I can't find the fix of the problem).
My problem: I have a library that gives emits certain events (in this case a Bluetooth library that handles connection, pairing, data processing, etc...). The activities interested in these events can subscribe to certain or all of them to get notified when the library has something. I guess it is important to say that I AM getting responses from my library (so data), but I cannot change the UI (I can add words to a textview but that's about it) when my activity receives the response. I do understand that the threads that onNext() will run on is where the subscribe was called for, and as of right now, the subscribe on is NOT explicitly called by my activity ( I'll paste in the code below).
Snippet of activity registration to events:
EventBus.subscribe(EventBus.Events.CONNECTED, this, new Consumer<Object>()
{
#Override
public void accept(Object device)
{
if(device instanceof DeviceStructure)
{
if(uuid.equals(((DeviceStructure) device).getUUID()))
{
status_value.append("device connected " + ((DeviceStructure) device).getUUID());
}
else if(name.equals(((DeviceStructure) device).getName()))
{
status_value.append( "device connected " + name + " " + address);
}
}
}
}
);
EventBus.subscribe(EventBus.Events.PAIRED, this, new Consumer<Object>()
{
#Override
public void accept(Object device)
{
if(device instanceof DeviceStructure)
{
if(((DeviceStructure) device).getPairedStatus())
{
uuid = ((DeviceStructure) device).getUUID();
status_value.append("device paired " + uuid);
pair.setEnabled(false);
sync.setEnabled(true);
unpair.setEnabled(true);
}
}
}
});
I am able to modified the status_value values and display some text. However, I am unable to do pair.setEnabled(false) ( The part where the error gets thrown)
The event bus class:
public final class EventBus {
public enum Events
{
CONNECTED,
PAIRED
}
private static Map<Events, PublishSubject<Object>> sSubjectMap = new HashMap<>();
private static Map<Object, CompositeDisposable> sSubscriptionsMap = new HashMap<>();
private EventBus() {
// hidden constructor
}
/**
* Get the subject or create it if it's not already in memory.
*/
#NonNull
private static PublishSubject<Object> getSubject(Events event) {
PublishSubject<Object> subject = sSubjectMap.get(event);
if (subject == null) {
subject = PublishSubject.create();
subject.subscribeOn(AndroidSchedulers.mainThread());
sSubjectMap.put(event, subject);
}
return subject;
}
/**
* Get the CompositeDisposable or create it if it's not already in memory.
*/
#NonNull
private static CompositeDisposable getCompositeDisposable(#NonNull Object subscriber) {
CompositeDisposable compositeDisposable = sSubscriptionsMap.get(subscriber);
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
sSubscriptionsMap.put(subscriber, compositeDisposable);
}
return compositeDisposable;
}
/**
* Subscribe to the specified subject and listen for updates on that subject. Pass in an object to associate
* your registration with, so that you can unsubscribe later.
* <br/><br/>
*/
public static void subscribe(Events subject, #NonNull Object subscriber, #NonNull Consumer<Object> action) {
Disposable disposable = getSubject(subject).subscribe(action);
getCompositeDisposable(subscriber).add(disposable);
}
/**
* Unregisters this object from the bus, removing all subscriptions.
* This should be called when the object is going to go out of memory.
*/
public static void unregister(#NonNull Object lifecycle) {
//We have to remove the composition from the map, because once you dispose it can't be used anymore
CompositeDisposable compositeDisposable = sSubscriptionsMap.remove(lifecycle);
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
}
/**
* Publish an object to the specified subject for all subscribers of that subject.
*/
public static void publish(Events subject, #NonNull Object message) {
getSubject(subject).onNext(message);
}
}
I have tried the following solutions proposed online to indicate that I would like the subscriber to run on the main thread ( which should be the UI thread, unless I got that wrong).
subject.observeOn(AndroidSchedulers.mainThread());
subject.delay(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread());
subject.subscribeOn(AndroidSchedulers.mainThread());
subject.subscribeOn(AndroidSchedulers.from(Looper.getMainLooper()));
I have also found this link : https://github.com/ReactiveX/RxAndroid/issues/371 but the solution provided still doesn't seem to like what my activity is doing.
I guess it is also worth noting that when I enter the accept method in PAIRED action, the thread isn't the UI thread at that point.
Edit:
I'll leave this on just for other people like me. I had to add the observeOn to my disposable object and not in my getSubject() method which is where I added it from.
if (subject == null) {
subject = PublishSubject.create();
subject.subscribeOn(AndroidSchedulers.mainThread());
The result of subscribeOn is ignored.
Technically what you must do is somehow interject an observeOn(AndroidSchedulers.mainThread()) before you receive your event from the relay to the subscriber.
A possibility is to define an Observable.Transformer that you can compose which will apply the observeOn.
#SuppressWarnings("unchecked")
<T> Observable.Transformer<T, T> observeOnMain() {
return tObservable -> tObservable.observeOn(AndroidSchedulers.mainThread());
}
and
.compose(observeOnMain())
.subscribe(subscriber);
I have a simple use case where:
Activity1 create a fragment1
fragment1 after creation notify to activity that it is created and update its activity1 views.
activity1 after getting notification update fragment1 views.
I am using rxandroid , sublibrary rxlifecycle components and android , but i am still in learning phase , there was not even rx-lifecycle tag on stackoverflow , so i am still struggling to understand the flow of this library..
Edit
I prefer not to use EventBus , it's just like everyone shouting at everyone to do something, so Rxjava Observable approach will be much useful
For posting information from fragment to activity, you should use an event bus for informing activity about fragment creation (replacement to the callbacks and the mess they created).
Sample code for event bus with RxJava is:
public class SampleEventsBus {
private static final String TAG = SampleEventsBus.class.getSimpleName();
private static final String TAG2 = SampleEventsBus.class.getCanonicalName();
public static final int ACTION_FRAGMENT_CREATED = 1;
public static final int ACTION_FRAGMENT_OTHER = 2;
private static SampleEventsBus mInstance;
public static SampleEventsBus getInstance() {
if (mInstance == null) {
mInstance = new SampleEventsBus();
}
return mInstance;
}
private SampleEventBus() {}
private PublishSubject<Integer> fragmentEventSubject = PublishSubject.create();
public Observable<Integer> getFragmentEventObservable() {
return fragmentEventSubject;
}
public void postFragmentAction(Integer actionId) {
fragmentEventSubject.onNext(actionId);
}
}
Then from your fragment you can call:
SampleEventsBus.getInstance().postFragmentAction(SampleEventsBus.ACTION_FRAGMENT_CREATED);
from onAttach() or onViewCreated() or any place you prefer.
Also, in activity you will need to put the following code to listet to your event bus:
SampleEventsBus .getInstance().getFragmentEventObservable().subscribe(new Subscriber<Integer>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Integer actionId) {
if(actionId == SampleEventsBus.ACTION_FRAGMENT_CREATED) {
//do any required action
}
}
});
For the second part, i.e. to update the fragment from activity, I won't recommend using this method as it will lead to unnecessary complexity, Instead use the "original way" as:
Create a method in Fragment as updateView(Object obj)
In onNext(), get the desired fragment as SampleFragment fragment = (SampleFragment)getSupportFragmentManager().findFragmentByTag("TAG");
call fragment.updateView(obj);
Hope this helps.
Two points to consider:
Just because you use an EventBus does not mean that it needs to be
global. You can have multiple event buses if you want, and you can just
share a single one between two components (Activity and Fragment).
There are several examples in the RxJava documentation that show
how to implement event bus functionality using RxJava
By Using an event bus, you can simplify things greatly, by disassociating the whole process from the Android lifecycle.
I'm start learning RxJava and I like it so far. I have a fragment that communicate with an activity on button click (to replace the current fragment with a new fragment). Google recommends interface for fragments to communicate up to the activity but it's too verbose, I tried to use broadcast receiver which works generally but it had drawbacks.
Since I'm learning RxJava I wonder if it's a good option to communicate from fragments to activities (or fragment to fragment)?. If so, whats the best way to use RxJava for this type of communication?. Do I need to make event bus like this one and if that's the case should I make a single instance of the bus and use it globally (with subjects)?
Yes and it's pretty amazing after you learn how to do it. Consider the following singleton class:
public class UsernameModel {
private static UsernameModel instance;
private PublishSubject<String> subject = PublishSubject.create();
public static UsernameModel instanceOf() {
if (instance == null) {
instance = new UsernameModel();
}
return instance;
}
/**
* Pass a String down to event listeners.
*/
public void setString(String string) {
subject.onNext(string);
}
/**
* Subscribe to this Observable. On event, do something e.g. replace a fragment
*/
public Observable<String> getStringObservable() {
return subject;
}
}
In your Activity be ready to receive events (e.g. have it in the onCreate):
UsernameModel usernameModel = UsernameModel.instanceOf();
//be sure to unsubscribe somewhere when activity is "dying" e.g. onDestroy
subscription = usernameModel.getStringObservable()
.subscribe(s -> {
// Do on new string event e.g. replace fragment here
}, throwable -> {
// Normally no error will happen here based on this example.
});
In you Fragment pass down the event when it occurs:
UsernameModel.instanceOf().setString("Nick");
Your activity then will do something.
Tip 1: Change the String with any object type you like.
Tip 2: It works also great if you have Dependency injection.
Update:
I wrote a more lengthy article
Currently I think my preferred approach to this question is this to:
1.) Instead of one global bus that handles everything throughout the app (and consequently gets quite unwieldy) use "local" buses for clearly defined purposes and only plug them in where you need them.
For example you might have:
One bus for sending data between your Activitys and your ApiService.
One bus for communicating between several Fragments in an Activity.
One bus that sends the currently selected app theme color to all Activitys so that they can tint all icons accordingly.
2.) Use Dagger (or maybe AndroidAnnotations if you prefer that) to make the wiring-everything-together a bit less painful (and to also avoid lots of static instances). This also makes it easier to, e. g. have a single component that deals only with storing and reading the login status in the SharedPreferences - this component could then also be wired directly to your ApiService to provide the session token for all requests.
3.) Feel free to use Subjects internally but "cast" them to Observable before handing them out to the public by calling return subject.asObservable(). This prevents other classes from pushing values into the Subject where they shouldn't be allowed to.
Define events
public class Trigger {
public Trigger() {
}
public static class Increment {
}
public static class Decrement {
}
public static class Reset {
}
}
Event controller
public class RxTrigger {
private PublishSubject<Object> mRxTrigger = PublishSubject.create();
public RxTrigger() {
// required
}
public void send(Object o) {
mRxTrigger.onNext(o);
}
public Observable<Object> toObservable() {
return mRxTrigger;
}
// check for available events
public boolean hasObservers() {
return mRxTrigger.hasObservers();
}
}
Application.class
public class App extends Application {
private RxTrigger rxTrigger;
public App getApp() {
return (App) getApplicationContext();
}
#Override
public void onCreate() {
super.onCreate();
rxTrigger = new RxTrigger();
}
public RxTrigger reactiveTrigger() {
return rxTrigger;
}
}
Register event listener wherever required
MyApplication mApp = (App) getApplicationContext();
mApp
.reactiveTrigger() // singleton object of trigger
.toObservable()
.subscribeOn(Schedulers.io()) // push to io thread
.observeOn(AndroidSchedulers.mainThread()) // listen calls on main thread
.subscribe(object -> { //receive events here
if (object instanceof Trigger.Increment) {
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) + 1));
} else if (object instanceof Trigger.Decrement) {
if (Integer.parseInt(fabCounter.getText().toString()) != 0)
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) - 1));
} else if (object instanceof Trigger.Reset) {
fabCounter.setText("0");
}
});
Send/Fire event
MyApplication mApp = (App) getApplicationContext();
//increment
mApp
.reactiveTrigger()
.send(new Trigger.Increment());
//decrement
mApp
.reactiveTrigger()
.send(new Trigger.Decrement());
Full implementation for above library with example -> RxTrigger