An Activity contains a Fragment which in turn contains a child Fragment, which requests a Service. The app tries to implement dobjanschi rest architecture.
When the Service is done working it has to propagate operation result. I tried using a PendingIntent but it seems to only be caught by the activity, while I need the child fragment to get notified. Could you suggest anything? Binder? greenRobot Eventbus? RxJava (which I already have in the project)?
Thanks.
RxJava
A simple way con be to use a Singleton to wrap a synchronized ´PublishSubject´
* Singleton
*
* to send an event use EventBusRx.getInstance().topic1.onNext("completed");
*/
public class EventBusRx {
private static EventBusRx ourInstance = new EventBusRx();
public static EventBusRx getInstance() {
return ourInstance;
}
private EventBusRx() {}
/**
* Use of multiple topics can be usefull
* SerializedSubject avoid concurrency issues
*/
public final Subject<String, String> topic1 = new SerializedSubject<>(PublishSubject.create());
public final Subject<Integer, Integer> topic2 = new SerializedSubject<>(PublishSubject.create());
}
And You can send events from service
EventBusRx.getInstance().topic1.onNext("completed");
and respond to event in fragments or whenever you want
public class MyFragment extends Fragment {
// [...]
Subscription subscription_topic1;
#Override
public void onResume() {
super.onResume();
subscription_topic1 = EventBusRx.getInstance().topic2
.subscribeOn(AndroidSchedulers.mainThread()) // or on other sheduler
.subscribe(new Action1<Integer>() {
#Override
public void call(Integer integer) {
// update ui
}
});
}
#Override
public void onPause() {
// important to avoid memory leaks
subscription_topic1.unsubscribe();
super.onPause();
}
}
do not forget to unsubcribe the Subscription
The idea is similar to Roger'one use a singleton but enforce ThreadSafety wrapping PublishSubject.
there is no need for Observable.switchOnNext(subject)
EventBus Libraries
greenRobot Eventbus and Otto are nice and has the same functionality, but the disadvantage is that they make the connection more smoky (expecialy EventBus) . If you already use rx i think is better to stay with it
Here is an insipring article about the topic
Implementing an Event Bus With RxJava
LocalBroadcast
The classic way to do this is to use LocalBroadcastManager but in my aopinion they are a pain
I would suggest using an Event Bus for this sort of thing. It will allow you to send messages to components within your system, without requiring creating special handlers.
Otto is a popular open source library for this, and there are others. http://square.github.io/otto/
Try this way hope it help you.
For Example:
YourService
public class MyService extends Service{
public static MyServiceListener getMyServiceListener() {
return MyService.myServiceListener;
}
public static void setMyServiceListener(MyServiceListener myServiceListener) {
MyService.myServiceListener = myServiceListener;
}
private static MyServiceListener myServiceListener;
public interface MyServiceListener{
void onResult(String response);
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
executeYourTask();
}
private void executeYourTask(){
String result = "SomeResultMaybeFromServer";
if(getMyServiceListener()!=null){
getMyServiceListener().onResult(result);
}
}
}
YourFragment
public class MyFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = null; // some view
// Start service
MyService.setMyServiceListener(new MyService.MyServiceListener() {
#Override
public void onResult(String response) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
// To handle memory/window leaks
}
});
}
});
return v;
}
}
I'm currently developing a Bus based solely on RxJava. Since you already have RxJava on your project, you can use it for this. You should use a BehaviorSubject and Observable.switchOnNext().
For example:
private BehaviorSubject<Observable<Whatever>> subject = BehaviorSubject.create();
public void post(){
subject.onNext(...);
}
public Observable<Whatever> subscribe(){
return Observable.switchOnNext(subject);
}
You should have this as part of a Singleton so the same BehaviorSubject is used. All you have to do is post() from one fragment and subscribe() on the other one or in any other interested fragment or activity. You can have as many subscriptions as you want, plus if you implement it correctly then the last emitted Observable will survive orientation changes.
More info on BehaviorSubject can be found here: https://github.com/ReactiveX/RxJava/wiki/Subject
I'm currently using this Pub/Sub pattern with rxjava and enum class.
public enum Events {
public static PublishSubject <Object> myEvent = PublishSubject.create ();
}
//where you want to publish something
Events.myEvent.onNext(myObject);
//where you want to receive an event
Events.myEvent.subscribe (...);
I would use event bus, which is based on rx.
Make this as a sigletone and subscribe on particular class type.
public class RxBus {
private static final RxBus sBus = new RxBus();
private final Subject<Object, Object> mBus = new SerializedSubject<>(PublishSubject.create());
private RxBus() {
}
public static RxBus getInstance() {
return sBus;
}
public void send(Object o) {
mBus.onNext(o);
}
public Observable<Object> observe() {
return mBus;
}
#SuppressWarnings("unchecked")
public <T> Observable<T> observe(Class<T> c) {
return mBus.filter(o -> c.isAssignableFrom(o.getClass())).map(o -> (T) o);
}
}
usage:
class Message { public String result};
send a message:
Message m = new Message();
m.result = "Hello world";
RxBus.getInstance().send(m);
subscribe on a particular class type:
RxBus.getInstance().observe(Message.class).subscribe(msg -> Log.e(TAG, "Message was caught : " + msg));
Related
I'm building an Android app, and new to Rxjava, having a beginner's question:
How can i subscribe to an event on activity starts, and close the subscription on activity stops?
I confused how to implement it.
You have to create an instance of PublishSubject and subscribe to it. and when you want to send and event you need to call onNext() of the PublishSubject instance.
Note: you have to hold one instance for each type of subscriptions.
First of all you must create EventSubscription class to hold specific PublishSubject instance:
EventSubscription.java:
public class EventSubscription {
private static EventSubscription instance;
private PublishSubject<String> subject = PublishSubject.create();
/**
* Singleton
*/
public static EventSubscription getInstance() {
if (instance == null) {
instance = new EventSubscription();
}
return instance;
}
/**
* Pass any event down to event listeners.
*/
public void setString(String eventName) {
subject.onNext(eventName);
}
/**
* Subscribe to this Observable. On event
*/
public Observable<String> getEvents() {
return subject;
}
}
And in your activity you also need to hold a subscription instance to be able to unsubscribe it on activity stop. see the following activity class:
public class MyActivity extends Activity {
private Subscription mMySubscription;
#Override
public void onStart(){
// Start the subscription
mMySubscription = EventSubscription.getInstance().getEvents()
.subscribe(new Action1<String>() {
#Override
public void call(String eventName){
// Handle new event
}
});
}
#Override
public void onStop(){
if(mMySubscription != null && ! mMySubscription.isUnsubscribed()){
// if your subscription already unsubscribed
mMySubscription.unsubscribe();
}
}
}
I have one doubt about using services. I have a service that initializes an object, is it a bad practice to pass an instance of the service to the object so it can be used for that object? A simplified object would be:
public class MyService extends Service {
MyObject myObject = new MyObject(this);
...
}
public MyObject {
private MyService myService;
public MyObject(MyService myService) {
this.myService = myService;
}
...
private void exampleMethod() {
myService.method();
}
}
What do you think? Is it a bad practice? How could I solve that issue without passing the service's instance?
The fact is that I want to split the code in two classes because the features are different, the websocket is connected from the service class, but the methods to parse/send events through the websocket are in the second class. I want to do this way in order to avoid having one class with 2000 lines of code, and by splitting the code by features. The service handles the websocket connection, while the other class handles the other features. As everything is asynchronous, the second class needs an instance of the service class. For instance: if an error is received and parsed (on the second class), this second class must call the service class to update its status and do a reconnection.
EDIT:
I'm thinking about implementing the following solution:
public class MyService extends Service {
MyObject myObject = new MyObject() {
protected void onSuccess() {
...
}
};
...
}
public abstract class MyObject {
public MyObject() {
}
protected abstract void onSuccess();
...
private void exampleMethod() {
...
onSuccess()
}
}
The more I think about it, the better solution I think it is. What do you think?
Thank you very much in advance!
This makes no sense at all. I suggest you to use a interface if you need to pass a callback to a dao (the websocket controller). The thing is that you should use your service to implement your websocket controller.
Please add the websocket code, so we can suggest more changes.
EDIT:
public interface onGetData {
public void onSuccess(Object response) // here you pass the obj type you need in your service
public void onError(Object error) // same, but if things fail
}
public class MyService extends Service implements onGetData {
#Override
public void OnCreate() {
MyObject myObject = new MyObject(this);
}
#Override
public void onSuccess(Object response) {
}
#Override
public void onError(Object error) {
}
}
public MyObject {
private OnGetData onGetData ;
public MyObject(OnGetData onGetData) {
this.onGetData = onGetData;
}
private void onRequestSuccess(Object response) {
onGetData.onSuccess(response)
}
private void onRequestError(Object error) {
onGetData.onError(error)
}
}
I am using the Model-View-Presenter design pattern coupled with an EventBus (Otto). The entire reason I implemented this pattern is to decouple events to the presenter only, and have the presenter update the views.
This is an example of some of the code I have, I'll use getting Events as an example. (Please note that Events is different from the EventBus Event, meaning an Event in Events is an event like "Dad's Birthday", but an Event in the EventBus is a Bus-event).
Fragment
public class EventFragment extends Fragment {
private EventPresenter mEventPresenter;
// Initialize boilerplate code...
private void init() {
mEventPresenter = new EventPresenter();
mEventPresenter.loadEvents();
}
// I WANT TO MOVE THESE SUBSCRIPTION METHODS TO
// MY PRESENTER OR SUBSCRIBER, BUT THEY ARE
// COUPLED TO THE ADAPTER OR A VIEW
#Subscribe
public void onEventsLoaded(EventsLoaded eventsLoaded) {
List<Event> events = eventsLoaded.getEvents();
mAdapter.setEvents(events);
}
#Subscribe
public void onEventJoined(EventJoined eventJoined) {
mItemView.doAnimation();
mTextView.setText("Leave");
mAdapter.joinEvent(eventJoined.getEvent());
}
#Subscribe
public void onLeaveEvent(LeftEvent leftEvent) {
mItemView.doAnimation();
mTextView.setText("Join");
mAdapter.leftEvent(leftEvent.getEvent());
}
}
Presenter
public class EventPresenter {
// This is the only method I have right now kind of defeats the purpose of
// having a presenter
public void loadEvents() {
EventBus.getInstance().post(new LoadEvents());
}
}
Subscriber
public class EventSubscriber extends Subscriber {
// This class is registered on the event bus
#Subscribe
public void onLoadEvents(LoadEvents loadEvents) {
sClient.getEvents(new Callback<List<Event>>() {
#Override
public void onSuccess(List<Event> events, Response response) {
EventBus.post(new EventsLoaded(events));
}
#Override
public void onFailure(.....) {
// Handle failure
}
};
}
}
How can I get the Presenters and the Subscribers to handle all the business logic, and have the Fragment only handle views?
This is not the best possibility but May Be help you out.
subscribing events in presenter , with an instance of view inside it can help. Like presenter gets events from eventbus and call appropriate view method to update the UI.
Mind that I have not changed method names of your fragment that updates UI and have passed event object directly from presenter which is indeed now a subscriber of events of eventbus. You can change it accordingly.
Fragment---
public class EventFragment extends Fragment {
private EventPresenter mEventPresenter;
// Initialize boilerplate code...
#Override
public void onResume()
{
mEventPresenter.onResume();
}
#Override
public void onPause()
{
mEventPresenter.onPause();
}
private void init() {
mEventPresenter = new EventPresenter(this);
mEventPresenter.loadEvents();
}
// I WANT TO MOVE THESE SUBSCRIPTION METHODS TO
// MY PRESENTER OR SUBSCRIBER, BUT THEY ARE
// COUPLED TO THE ADAPTER OR A VIEW
public void onEventsLoaded(EventsLoaded eventsLoaded) {
List<Event> events = eventsLoaded.getEvents();
mAdapter.setEvents(events);
}
public void onEventJoined(EventJoined eventJoined) {
mItemView.doAnimation();
mTextView.setText("Leave");
mAdapter.joinEvent(eventJoined.getEvent());
}
public void onLeaveEvent(LeftEvent leftEvent) {
mItemView.doAnimation();
mTextView.setText("Join");
mAdapter.leftEvent(leftEvent.getEvent());
}
}
Presenter----
public class EventPresenter {
private EventFragment targetView;
public EventPresenter(EventFragment myView)
{
targetView = myView;
}
// This is the only method I have right now kind of defeats the purpose of
// having a presenter
public void loadEvents() {
EventBus.getInstance().post(new LoadEvents());
}
#Subscribe
public void onEventsLoaded(EventsLoaded eventsLoaded) {
targetView.onEventsLoaded(eventsLoaded);
}
#Subscribe
public void onEventJoined(EventJoined eventJoined) {
targetView.onEventJoined(eventJoined);
}
#Subscribe
public void onLeaveEvent(LeftEvent leftEvent) {
targetView.onLeaveEvent(leftEvent);
}
public void onResume()
{
//subscride event bus
}
public void onPause()
{
//unsubscride event bus
}
}
This is responsability to presenters to send or receive events and after to notify fragment or activities
I'm looking at retrofit for my networking layer. Is there any way to tell if a particular async request is running at any given moment?
For example, I'd like to know if a request is running so that I can update the user interface at various times. I could do this myself by keeping variables around to track state, but wondering if there's something already in the library for this.
Here is what I would normally do when needing a way to keep track of running requests:
First, using retrofit, as soon as you make the request, you can do the following:
Use EventBus library to post an event to your activity or fragment. Now, this can be done inside onSuccess() method of your Callback or onError() method of the same.
In your activity or fragment's onEvent(EventClassName event) method, you can simply check a variable like [isRunning] from your event to make sure that if the event is still running, you update the UI accordingly and if not, do what you need to do respectively.
When the request is completed, obviously isRunning will be false and you can then update the UI as expected by the user.
I am recommending EventBus here simply because it is much easier to decouple your application code with it; you can send different events that notify the activity of the different statuses of your requests and then update your UI that way.
You can find EventBus here
I hope this helps!
What I personally ended up doing in this case was that I was running the example with Retrofit, Android Priority Jobqueue (from yigit's fork) and Otto eventbus.
public enum SingletonBus {
INSTANCE;
private Bus bus;
private Handler handler = new Handler(Looper.getMainLooper());
private SingletonBus() {
this.bus = new Bus(ThreadEnforcer.ANY);
}
public <T> void postToSameThread(final T event) {
bus.post(event);
}
public <T> void postToMainThread(final T event) {
handler.post(new Runnable() {
#Override
public void run() {
bus.post(event);
}
});
}
public <T> void register(T subscriber) {
bus.register(subscriber);
}
public <T> void unregister(T subscriber) {
bus.unregister(subscriber);
}
}
public interface Interactor {
void injectWith(PresenterComponent presenterComponent);
}
public interface SendCertificateRequestInteractor
extends Interactor {
interface Listener {
void onSuccessfulEvent(SuccessfulEvent e);
void onFailureEvent(FailureEvent e);
}
class SuccessfulEvent
extends EventResult<CertificateBO> {
public SuccessfulEvent(CertificateBO certificateBO) {
super(certificateBO);
}
}
class FailureEvent
extends EventResult<Throwable> {
public FailureEvent(Throwable throwable) {
super(throwable);
}
}
void sendCertificateRequest(String username, String password);
}
Pay attention to the Job here:
public class SendCertificateRequestInteractorImpl
implements SendCertificateRequestInteractor {
private Presenter presenter;
private boolean isInjected = false;
#Inject
public JobManager jobManager;
public SendCertificateRequestInteractorImpl(Presenter presenter) {
this.presenter = presenter;
}
#Override
public void sendCertificateRequest(String username, String password) {
if(!isInjected) {
injectWith(presenter.getPresenterComponent());
isInjected = true;
}
InteractorJob interactorJob = new InteractorJob(presenter, username, password);
long jobId = jobManager.addJob(interactorJob); //this is where you can get your jobId for querying the status of the task if you want
}
#Override
public void injectWith(PresenterComponent presenterComponent) {
presenterComponent.inject(this);
}
public static class InteractorJob
extends Job {
private final static int PRIORITY = 1;
private final static String TAG = InteractorJob.class.getSimpleName();
private String username;
private String password;
#Inject
public MyService myService;
public InteractorJob(Presenter presenter, String username, String password) {
super(new Params(PRIORITY).requireNetwork());
presenter.getPresenterComponent().inject(this);
this.username = username;
this.password = password;
}
#Override
public void onAdded() {
// Job has been saved to disk.
// This is a good place to dispatch a UI event to indicate the job will eventually run.
// In this example, it would be good to update the UI with the newly posted tweet.
}
#Override
public void onRun()
throws Throwable {
String certificate = myService.getCertificate(username, password);
SingletonBus.INSTANCE.postToMainThread(new SuccessfulEvent(certificate));
}
#Override
protected void onCancel() {
// Job has exceeded retry attempts or shouldReRunOnThrowable() has returned false.
Log.e(TAG, "Cancelled job.");
}
#Override
protected boolean shouldReRunOnThrowable(Throwable throwable) {
// An error occurred in onRun.
// Return value determines whether this job should retry running (true) or abort (false).
Log.e(TAG, "Failed to execute job.", throwable);
SingletonBus.INSTANCE.postToMainThread(new FailureEvent(throwable));
return false;
}
}
}
And then
#Subscribe
#Override
public void onSuccessfulEvent(SendCertificateRequestInteractor.SuccessfulEvent e) {
String certificate = e.getResult();
//do things
}
#Subscribe
#Override
public void onFailureEvent(SendCertificateRequestInteractor.FailureEvent e) {
Throwable throwable = e.getResult();
//handle error
}
More about android priority jobqueue here.
This way, technically the async handling is referred to the job queue, while Retrofit itself is using the synchronous interface. It works well as long as you don't need to access the headers of the response. Although to be fair, I was also keeping track of whether the job was running with a boolean instead of the job manager and the id as well..
Also, I haven't figured out how to use dependency injection properly with persisted jobs; nor do I really know how they intended to make that work. Of course, it'd work if it was using the application scoped component rather than a supplied presenter scoped one, but that is irrelevant.
You'll probably need to customize this solution to your own scenario, and use only what you actually need.
I seem to be having one of two issues here. Either an error like:
java.lang.IllegalStateException: Event bus [Bus "default"] accessed from non-main thread Looper
Or, if I've managed to "address" that, I simply never receive the event in my subscriber.
Currently, I have a class, cobbled from a few sources, sub-classing Bus:
public class MainThreadBus extends Bus {
private static Bus _bus;
private Handler _handler = new Handler(Looper.getMainLooper());
public MainThreadBus() {
if (_bus == null) {
_bus = new Bus();
}
}
#Override public void register(Object obj) {
_bus.register(obj);
}
#Override public void unregister(Object obj) {
_bus.unregister(obj);
}
#Override public void post(final Object event) {
if (Looper.myLooper() == Looper.getMainLooper()) {
_bus.post(event);
} else {
_handler.post(new Runnable() {
#Override public void run() {
_bus.post(event);
}
});
}
}
}
Which is used in an Activity like this:
#Subscribe
public void requestProgressAvailable(RESTRequestProgress progress) {
processProgress(progress);
}
#Override
protected void onResume() {
super.onResume();
_bus = new MainThreadBus();
_bus.register(this);
}
#Override
protected void onPause() {
super.onPause();
_bus = new MainThreadBus();
_bus.unregister(this);
}
And publishing from an IntentService like this:
_bus = new MainThreadBus();
_bus.post(request.createRESTRequestProgress(RESTRequest.STATUS_STARTED));
And the messages are not received. An alternate configuration had me receiving the thread error, so I'm going with this, for now.
So, what am I missing, or doing wrong?
EDIT: Thanks to Andy below for pointing out that my code wasn't acting as I thought it was. The code above now reflects modifications based on that feedback.
Aside from the fact this implementation isn't a Singleton, when getting this error, you can use the threadEnforcer.ANY option in the constructor:
private static final Bus BUS = new Bus(ThreadEnforcer.ANY);
The problem is that your code is not interacting with the same bus instance. Instead of creating a new MainThreadBus in each case, you should access the same bus, for example a singleton obtained from a factory or via injection.