I need to pass some data between two activities MainActivity and ChildActivity. Button click on MainActivity should open ChildActivity and send event with data. I have singleton:
Subject<Object, Object> subject = new SerializedSubject<>(PublishSubject.create());
and in MainActivity I have the following button click handler:
public void onClick(){
startActivity(new Intent(MainActivity.this, ChildActivity.class));
subject.onNext(new SomeEvent(data));
}
and event listener subscription in ChildActivity :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addEventListeners();
}
private void addEventListeners() {
subject.ofType(SomeEvent.class)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribe(
event -> {
loadData(event.getData());
});
}
When I send event after starting activity and call addEventListeners in ChildActivity onCreate is still not subscribed to this event and loadData() is not called.
What is proper way to pass data between activities using RxJava (if it's possible)?
if anybody needs a complete solution to send data between activities using RxJava2
1- Create the bus:
public final class RxBus {
private static final BehaviorSubject<Object> behaviorSubject
= BehaviorSubject.create();
public static BehaviorSubject<Object> getSubject() {
return behaviorSubject;
}
}
2- the sender activity
//the data to be based
MyData data =getMyData();
RxBus.getSubject().onNext(data) ;
startActivity(new Intent(MainActivity.this, AnotherAct.class));
3-the receiver activity
disposable = RxBus.getSubject().
subscribeWith(new DisposableObserver<Object>() {
#Override
public void onNext(Object o) {
if (o instanceof MyData) {
Log.d("tag", (MyData)o.getData();
}
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
});
4-unSubscribe to avoid memory leacks:
#Override
protected void onDestroy() {
super.onDestroy();
disposable.dispose();
}
Reason:
Problem is that you are using PublishSubject. As per documentation of PublishSubject emits all the subsequent items of the source Observable at the time of the subscription. So in your case it will emit event only if it is subscribed.
Fix for your problem
Instead of using PublishSubject use BehaviorSubject which emits the most recently emitted item and all the subsequent items of the source Observable when a observer subscribe to it.
Browse following link for more details.
Related
The way I'm getting callbacks from network requests is via interfaces.
Suppose there are two classes, A & B. Class A initiates all network requests which are performed by B. When B finishes the task, it has to respond to A.
The way I do it is:
public interface MyCallback {
void onTaskDone(String successMessage);
void onTaskFailed(String failMessage);
}
public class A {
onCreate() {
B objectB = new B();
objectB.loginUser(username, password, new MyCallback {
void onTaskDone(successmessage) {
//this is called when task is successful
}
void onTaskFailed(failMessage) {
//this is called when task is failed
});
}
}
}
public class B {
public void loginUser(String username, String password, MyCallback callback) {
//after task is performed
if (task.isSuccessful()) {
callback.onTaskDone("Successful");
} else {
callback.onTaskFailed("Programming is fun they said...");
}
}
}
As you can see, if a task is successful the interface methods are called from B which is received in A.
What my question is: Are there better ways to get callbacks besides using interfaces, or can this technique be made better? One issue I face while implementing this technique is, say I'm using same interface with many methods. In a particular case only one or two methods are used, while the rest remain unused, e,g. class B may never call onTaskFailed(). Is it normal that some methods are completely unused?
Android has a very good third party library like EventBus
https://github.com/greenrobot/EventBus
You can see its documentation, very easy to use.
public class A{
onCreate(){
B objectB = new B();
objectB.loginUser(username,password); //no need to pass callback
#Subscribe(threadMode = ThreadMode.MAIN)
public void onSuccessEvent(SuccessEvent successEvent) {
//this is called when task is successful
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onErrorEvent(ErrorEventsuccessEvent) {
//this is called when task is failed
}
}
public class B{
public void loginUser(String username, String password){
//after task is performed
if(task.isSuccessful()){
EventBus.getDefault().post(new SuccessEvent("Successful"));
}else{
EventBus.getDefault().post(new ErrorEvent("Programming is fun they said..."));
}
}
Your event classes
public class SuccessEvent {
private String message;
public SuccessEvent(String message) {
this.message=message;
}
}
public class ErrorEvent {
private String message;
public ErrorEvent(String message) {
this.message=message;
}
}
I found the answer to the question at the bottom: i.e the interface methods going unused.
To solve that I used abstract class! i.e an abstract class will implement all the interface callbacks.
Then while passing callback, simply pass the abstract class instance instead of the interface. This way, only the *required method can be overridden for getting the result.
So I'm fairly new to MVVM. So I'm fetching my data in my VM and I'm passing in the Activity/fragment as a listener in the method call.
The reason I'm doing this is because I'm going to have a callback if there was to be an error. So I'd handle it in the activity/fragment with a dialog.
I'm not sure if I'm breaking MVVM here? If i'm making any other errors with this pattern, please let me know.
Thanks
In my view, fragment/activity
/*creating and using my VM inside my fragment*/
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
//Create and observe data for any changes throughout the lifecycle
final OverviewViewModel viewModel = ViewModelProviders.of(this).get(OverviewViewModel.class);
//get info
viewModel.getUserInfo(this);
observeViewModel(viewModel);
}
//Listener in the activity/fragment that will handle an error in the request
#Override
public void onTokenExpired() {
ExpiredTokenDialogFragment dialogFragment = new ExpiredTokenDialogFragment();
dialogFragment.show(getFragmentManager(), EXPIRED_DIALOG);
}
My View model where i make request.
public void getUserInfo(AuthenticationListener listener){
mUserInformationObservable = mRepository.getUserInfo(listener);
}
My retrofit request
public LiveData<UserInformation> getUserInfo(final AuthenticationListener authenticationListener){
final MutableLiveData<UserInformation> data = new MutableLiveData<>();
mService.fetchFollowers().enqueue(new Callback<UserInformation>() {
#Override
public void onResponse(Call<UserInformation> call, retrofit2.Response<UserInformation> response) {
//note, the result is in data. Calling response.body.string twice results in an empty string
if(response.body()!=null) data.setValue(response.body());
}
#Override
public void onFailure(Call<UserInformation> call, Throwable t) {
if(t instanceof UnauthorizedException){
data.setValue(null);
mToken.setAccessToken(null);
authenticationListener.onTokenExpired();
}
}
});
return data;
}
Using a listener is not recommended. The android-architecture project uses a SingleLiveEvent class for events like navigation or displaying a Snackbar. You can use the same class for showing a dialog.
In your OverviewViewModel you can add another field:
final SingleLiveEvent<Void> tokenLiveData = SingleLiveEvent<Void>();
in your onFaliure callback you can use:
tokenLiveData.call()
instead of the callback.
In your activity subscribe to tokenLiveData and show a dialog when it emits a value.
I'm using MVVM on android application and i want to manage requests and rxJava on device rotation, how can i disable request after rotation device and countinue from last request?
this is my simple code to know how can i doing that, but i can't find any document and sample code about it
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_register);
...
Observer<String> myObserver = new Observer<String>() {
#Override
public void onError(Throwable e) {
// Called when the observable encounters an error
}
#Override
public void onComplete() {
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String s) {
// Called each time the observable emits data
Log.e("MY OBSERVER", s);
}
};
Observable.just("Hello").subscribe(myObserver);
}
I'm using latest version of rxJava
Handling rotation is a cool challenge in Android. There're a few ways to do that.
1- Services: You can use a service and handle your network requests or other background operations in service. Also with Services, you'll seperate your business logic from ui.
2- Worker Fragment: Worker fragment is a fragment instance without a layout. You should set your worker fragment's retainInstanceState to true. So you'll save your fragment from orientation change and will not lose your background operations.
Why Worker Fragment? If you set retainInstanceState true to a fragment with layout, you'll leak views.
If you're using MVVM you can implement ViewModel as a Worker Fragment which as setRetainInstanceState = true
3- Global Singleton Data Source: You can create a global singleton data source class which handles your operations in an independent scope from Activity / Fragment lifecycle in your application.
4- Loaders: Loaders can recover state from orientation changes. You handle your operations with loaders but they are designed to load data from disk and are not well suited for long-running network requests.
Extra: You can use Path's Priority Job Queue to persist your jobs:
https://github.com/path/android-priority-jobqueue
Edit: You can check my repo for handling device rotation without using Google's new architecture components. (As an example of Worker Fragment which i pointed in my answer.)
https://github.com/savepopulation/bulk-action
You have the following options:
Use some global Singleton, or your Application class, that holds your logic, not within your Activity's lifecycle
Use a Service that runs next to your activity/application
Use a Loader
Global state is often bad and makes your code hard to test / debug. Services tend to be overkill.
For your use case of device rotation and continuing where one left off you'd usually use a Loader, which keeps running on rotation and only gets destroyed once you leave the activity.
I also recently wrote an article about one possible solution to use Loaders together with RxJava to keep state during orientation changes.
You can take advantage of Fragment#setRetainInstance(true). With that flag set, fragment is not destroyed after device rotation and can be used as an object container. Please look at this sample which also stores Observable - https://github.com/krpiotrek/RetainFragmentSample
you need to override
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
When device is rotated store data in bundle then inside on create check
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null){
//saved instance is null
}else{
//get your stored values here
counter = savedInstanceState.getInt("value",0); //here zero is the default value
}
}
How I'm doing this is to have a singleton class (or any long living Object as explained by savepopulation earlier, but - the trick is to store the loaded data in a BehaviorSubject, and subscribe to that subject in the Activity instead of the original network request.
This way:
public class MyNetworkSingleton {
// This static service survives orientation changes
public static MyNetworkSingleton INSTANCE = new MyNetworkSingleton();
private final BehaviorSubject<String> dataSubject = BehaviorSubject.create();
public Observable<String> getData() {
if (!dataSubject.hasValue()) {
refreshData(); // No data is loaded yet, load initial data from network
}
return dataSubject;
}
public void refreshData() {
someDataSourceCall().subscribe(new Observer<String>() {
#Override
public void onError(Throwable e) {
// Remember, this point also needs error handling of some form,
// e.g. propagating the error to the UI as a Toast
}
#Override
public void onComplete() {
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String data) {
dataSubject.onNext(data); // this refreshes the internally stored data
}
});
}
private Observable<String> someDataSourceCall() {
return // some network request here etc. where you get your data from
}
}
and then:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Observer<String> myObserver = new Observer<String>() {
#Override
public void onError(Throwable e) {
// Called when the observable encounters an error
}
#Override
public void onComplete() {
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String s) {
// Called each time the observable emits data
Log.e("MY OBSERVER", s);
}
};
MyNetworkSingleton.INSTANCE.getData().subscribe(myObserver);
myRefreshButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
// refresh data from network only when button is pressed
MyNetworkSingleton.INSTANCE.refreshData();
}
});
}
This way only first time you need the data from network it will be loaded, or when the user clicks a refresh button (myRefreshButton).
In my app I use SyncAdapter(AbstractThreadedSyncAdapter) for synchronisation with server. Basically in background service I insert data to sql table, then on finish I want to inform UI to update ListView with new data. For this matter I tried to use GreenRobot EventBus, but no success.
my Event
public class SyncResultMsg {
public String message="";
public SyncResultMsg() {}
public SyncResultMsg(String value) {
this.message = value;
}
}
After insert data to database I call EventBus like this
SyncResultMsg event = new SyncResultMsg();
event.message = "groupsFetched";
EventBus.getDefault().post(event);
In my Fragment where I show ListView I try to receive EventBus like this:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
public void onEventMainThread(SyncResultMsg event) {
String msg = event.message;
if (msg.equals("groupsFetched")){
showNewData();
}
}
Try creating a custom EventBus with your own threadpool. Had a similiar issue and it solved it in my case.
Imagine you need to load and save some Data in one of your Fragments.
I want to use RX Java. How do you deal with multiple subscriptions on one Fragment ? AndroidObservable.bindFragment does the Job. But how can i use it when i need more subscriptions ?
public class MyFragment extends SomeFragment implements Observer<List<Item>>
{
private Subscription mReadSubscription;
private Subscription mWriteSubscription;
private JSONLoader mLoader;
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
final File theFile = new File(getActivity().getFilesDir(), FILE_NAME);
mLoader = new JSONLoader(theFile);
mReadSubscription = mLoader.getReadSubscription(this);
mWriteSubscription = mLoader.createWriteSubscription(new WriteObserver(), Collections.EMPTY_LIST);
The idea behind this is to save and load items using loader.load() loader.save(), each of this will result in an observer being used (mReadSubscription,mWriteSubscription).
The WriteObserver is just a simple Bean implementing the Observer again, but there is the part i do not understand: The #bindFragment Method checks for instances of Fragment. As WriteObserver is not an Fragment i cause an Exception. But i cant register a second observer because of Generics.
Im pretty sure i know to less about RX, anyone can point me in in a right direction to solve this ?
[Update 1]
There is my WriteObserver:
private final class WriteObserver implements Observer<Void> {
#Override
public void onCompleted() {
Toast.makeText(getActivity(), "Save Successful", Toast.LENGTH_SHORT).show();
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Void aVoid) {
}
}
That design isn't working as WriteObserver is not a Fragment, cause an Exception when you doing:
java.lang.IllegalArgumentException: Target fragment is neither a native nor support library Fragment
AndroidObservable.bindFragment(observer, source)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
So, how can i get around that limitation ?
As I understand, mLoader#getReadSubscription() and mLoader#createWriteSubscription() creates steams and subscribe to it.
I don't think that the right way to do it, as if mLoader subscribe to streams, it should deal how to unsubscribe to it too.
As you want to use AndroidObservable.bindFragment, mLoader#getReadSubscription() and mLoader#createWriteSubscription() should return Observable and not Subscription.
So, in your fragment, you'll be able to write :
#Override
public void onCreate(final Bundle savedInstanceState) {
// [...]
Observable<?> readObs = mLoader.getReadSubscription();
mReadSubscription = AndroidObservable.bindFragment(this, readObs).subscribe(this);
Observable<?> writeObs = mLoader.createWriteSubscription(Collections.emptyList());
mWriteSubscription = AndroidObservable.bindFragment(this, writeObs).subscribe(new WriteObserver());
}
So, how can i get around that limitation ?
It's not a limitation.
public static <T> Observable<T> bindFragment(Object fragment, Observable<T> source) {
// ...
}
The first argument should be your fragment, not your Observer. That why you should write your code as :
yourSubscription = AndroidObservable.bindFragment(yourFraglebt, yourObservable).subscribe(yourObserver);
so in your case :
AndroidObservable.bindFragment(this, source) // as bindFragment observeOn mainThread, you can remove the observeOn method call
.subscribeOn(Schedulers.io())
.subscribe(observer);
and not
AndroidObservable.bindFragment(observer, source)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);