I'm using RxJava3 and Live data.
I'm calling methos getAllMovies and getAllGenres in background thread, then set the received data to MutableLiveData.
In my splash activity I'm calling these methods from viewModel.
I have lottie animation , and each time lottie animation end, I'm checking if data received , if yes , opening another activity, else, waiting again for data receive.
The problem is when I lose internet connection , data is not received after the Internet is recovering.
I'm trying to run app when the wifi and internet is off, and then turning on wifi.
But always getting 0 in methods size.
If I running the app with internet on , data received normally.
That what I have.
App repository:
private MutableLiveData<List<GenreResult>> mGenresResponseMutableLiveData = new MutableLiveData<>();
public MutableLiveData<List<GenreResult>> getGenresResponseMutableLiveData() {
AppService appService = RetrofitInstance.getService();
appService.getAllGenres(mApplication.getResources().getString(R.string.api_key),
mApplication.getResources().getString(R.string.language))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<GenreResponse>() {
#Override
public void accept(GenreResponse genreResponses) throws Throwable {
mGenresResponseMutableLiveData.setValue(genreResponses.getGenreResults());
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Throwable {
Toast.makeText(mApplication.getApplicationContext(), throwable.getMessage(), Toast.LENGTH_LONG).show();
}
});
return mGenresResponseMutableLiveData;
}
ViewModel:
public class GenresViewModel extends AndroidViewModel {
private AppRepository mRepository;
public GenresViewModel(#NonNull Application application) {
super(application);
mRepository = new AppRepository(application);
}
public MutableLiveData<List<GenreResult>> getGenreLiveData() {
return mRepository.getGenresResponseMutableLiveData();
}
}
SplashActivity calling getGenre method:
private void getGenreList() {
mGenreResultArrayList = new ArrayList<>();
mGenresViewModel = new ViewModelProvider.AndroidViewModelFactory(getApplication()).create(GenresViewModel.class);
mGenresViewModel.getGenreLiveData().observe(this, new Observer<List<GenreResult>>() {
#Override
public void onChanged(List<GenreResult> genreResults) {
mGenreResultArrayList = (ArrayList<GenreResult>) genreResults;
}
});
}
SplashActivity checking on lottie listener if data received:
private void initViews() {
mLAVLoader = findViewById(R.id.lavLoader);
mLAVLoader.addAnimatorListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mGenreResultArrayList != null && mGenreResultArrayList.size() > 0
&& mMovieResultArrayList != null && mMovieResultArrayList.size() > 0) {
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putParcelableArrayListExtra("genreList", mGenreResultArrayList);
intent.putParcelableArrayListExtra("moviesList", mMovieResultArrayList);
startActivity(intent);
} else {
mLAVLoader.playAnimation();
Log.d("myDebug", "onAnimationEnd: " + mGenreResultArrayList.size()+ " "+mMovieResultArrayList.size());
}
}
});
}
I fixed it with retryWhen method in Repository:
public MutableLiveData<List<MovieResult>> getAllMoviesMutableLiveData() {
AppService appService = RetrofitInstance.getService();
appService.getAllMovies(mApplication.getResources().getString(R.string.api_key),
mApplication.getResources().getString(R.string.language),
"popularity.desc")
.retryWhen(throwable ->
throwable.delay(5, TimeUnit.SECONDS))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<MoviesResponse>() {
#Override
public void accept(MoviesResponse moviesResponse) throws Throwable {
mAllMoviesMutableLiveData.setValue(moviesResponse.getMovieResults());
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Throwable {
}
});
return mAllMoviesMutableLiveData;
}
Related
First of all, let me show the UI
First, query list of Head from remote server API1 by parameter tab1 and return list.
Second, traverse list and query the body list for every Head through API2 and combine the head and corresponding list.
Finally, after finish traversal, return List<Wrapper<Head, List>> data to show the UI.
It's better to use one RxJava block combine with Retrofit. My solution is as below, but it's not elegant enough. This should be a very common requirement so that it is better have a better solution for anyone to reference.
Thanks for all in advance. Hope I have described the requirement clearly.
First. query list of Header.
public void queryListData(LifecycleOwner lifecycleOwner, MaterialZone mz, int pageNum) {
VersaApiService.INSTANCE.getService(MaterialService.class)
.queryMaterialCategory(/*mz.getZoneId()*/"9223372036854775807", mVerticalPageNum, VERTICAL_PAGE_SIZE)
.compose(RxUtil.applyScheduler(lifecycleOwner))
.map(new Function<PageModel<Header>, List<Header>>() {
#Override
public List<Header> apply(#NonNull PageModel<Header> pageResult) throws Exception {
return pageResult.getResult().getRecords();
}
})
.subscribe(new Consumer<List<Header>>() {
#Override
public void accept(List<Header> headers) throws Exception {
mHeader.setValue(headers);
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
VLog.d(HORIZONTAL_PAGE_SIZE + ", " + VERTICAL_PAGE_SIZE);
VLog.d("onError");
}
}, new Action() {
#Override
public void run() throws Exception {
// Complete
VLog.d("onComplete");
}
});
}
Second. Query list of Body for every Header in traversal.
private MediatorLiveData<List<HeaderBodyWrapper>> mGroups = new MediatorLiveData<>();
private MutableLiveData<List<Header>> mHeader = new MutableLiveData<>();
public void init(LifecycleOwner lifecycleOwner) {
mGroups.addSource(mHeader, value -> {
queryBodyList(lifecycleOwner, value);
});
}
public void queryBodyList(LifecycleOwner lifecycleOwner, List<Header> headers) {
List<HeaderBodyWrapper> result = new ArrayList<>();
for (Header header : headers) {
VersaApiService.INSTANCE.getService(MaterialService.class)
.queryMaterial(header.getCategoryId(), mVerticalPageNum, VERTICAL_PAGE_SIZE)
.compose(RxUtil.applyScheduler(lifecycleOwner))
.map(new Function<PageModel<Body>, List<Body>>() {
#Override
public List<Body> apply(#NonNull PageModel<Body> materialGroupPageModel) throws Exception {
return materialGroupPageModel.getResult().getRecords();
}
})
.subscribe(new Consumer<List<Body>>() {
#Override
public void accept(List<Body> bodies) throws Exception {
// Combine header and body list.
HeaderBodyWrapper wrapper = new HeaderBodyWrapper();
wrapper.setMaterialGroup(header);
wrapper.setMaterials(bodies);
result.add(wrapper);
VLog.d("onNext");
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
VLog.d(HORIZONTAL_PAGE_SIZE + ", " + VERTICAL_PAGE_SIZE);
VLog.d("onError");
}
}, new Action() {
#Override
public void run() throws Exception {
// Complete
VLog.d("onComplete");
// TODO: Set value when all query.
if (result.size() == headers.size()) {
mGroups.setValue(result);
}
}
});
}
}
Here is my new solution without interacting with live data, but still is not elegant because of nested subscribe.
public void queryListData2(LifecycleOwner lifecycleOwner, MaterialZone mz) {
VersaApiService.INSTANCE.getService(MaterialService.class)
.queryMaterialCategory(/*mz.getZoneId()*/"9223372036854775807", mVerticalPageNum, VERTICAL_PAGE_SIZE)
.map(new Function<PageModel<Header>, List<Header>>() {
#Override
public List<Header> apply(#NonNull PageModel<Header> materialGroupPageModel) throws Exception {
return materialGroupPageModel.getResult().getRecords();
}
})
.concatMap(new Function<List<Header>, ObservableSource<List<HeaderBodyWrapper>>>() {
#Override
public ObservableSource<List<HeaderBodyWrapper>> apply(#NonNull List<Header> groups) throws Exception {
MaterialService ms = VersaApiService.INSTANCE.getService(MaterialService.class);
List<HeaderBodyWrapper> result = new ArrayList<>();
// Retrieve the list of header, then query their body list one by one.
// Combine the header and bodies and add into list, return the list
// to update UI finally.
// TODO: nested subscribe here is not elegant, try to combine observers
// below to be Observer<HeaderBodyWrapper>
for (Header group : groups) {
HeaderBodyWrapper mcw = new HeaderBodyWrapper();
Observable<PageModel<Body>> observable = ms.queryMaterial(group.getCategoryId(), 0, 100);
observable.subscribe(
new Consumer<PageModel<Body>>() {
#Override
public void accept(PageModel<Body> bodyPageModel) throws Exception {
mcw.setMaterialGroup(group);
PageModel.Result<Body> body = bodyPageModel.getResult();
if (body != null && body.getTotal() > 0) {
mcw.setMaterials(body.getRecords());
}
result.add(mcw);
}
},
new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
}
}, new Action() {
#Override
public void run() throws Exception {
}
});
}
// Observable.concat()
return Observable.just(result);
}
})
.compose(RxUtil.applyScheduler(lifecycleOwner))
.subscribe(new Consumer<List<HeaderBodyWrapper>>() {
#Override
public void accept(List<HeaderBodyWrapper> pageModel) throws Exception {
Gson gson = new Gson();
String result = gson.toJson(pageModel);
VLog.d(Thread.currentThread().getId() + " result");
VLog.d("onNext: " + result);
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
VLog.d(HORIZONTAL_PAGE_SIZE + ", " + VERTICAL_PAGE_SIZE);
VLog.d("onError");
}
}, new Action() {
#Override
public void run() throws Exception {
// Complete
VLog.d("onComplete");
}
});
}
I am working with MVVM pattern. I have just started it and i have done it successfully.
But I don't understand how to add progress bar for showing and hide as we normally do for API calls.
I am not using data binding. So how can i use progress bar for showing and hide it.
For Login
public class LoginRepository {
private DATAModel dataModel = new DATAModel();
private MutableLiveData<DATAModel> mutableLiveData = new MutableLiveData<>();
private Application application;
public LoginRepository(Application application) {
this.application = application;
}
public MutableLiveData<DATAModel> getMutableLiveData(String username, String password) {
APIRequest apiRequest = RetrofitRequest.getRetrofit().create(APIRequest.class);
JsonLogin jsonLogin = new JsonLogin(Constants.DEVICE_TYPE, Functions.getDeviceId(application.getApplicationContext()), Constants.APP_VERSION, Constants.API_VERSION, Functions.getTimeStamp(), Functions.getDeviceModel(), Build.VERSION.RELEASE, username, password);
Call<APIResponseLogin> call = apiRequest.getUsersDetails(jsonLogin);
call.enqueue(new Callback<APIResponseLogin>() {
#Override
public void onResponse(Call<APIResponseLogin> call, Response<APIResponseLogin> response) {
APIResponseLogin apiResponse = response.body();
if (apiResponse != null && apiResponse.getStatuscode() == 0) {
if (apiResponse.getDataModel() != null) {
dataModel = apiResponse.getDataModel();
mutableLiveData.setValue(dataModel);
}
} else if (apiResponse != null && apiResponse.getStatuscode() == 1) {
Log.v("AAAAAAAAA", apiResponse.getStatusmessage());
}
}
#Override
public void onFailure(Call<APIResponseLogin> call, Throwable t) {
Log.v("ErrorResponse", t.getMessage() + " : " + call.request().toString());
}
});
return mutableLiveData;
}
Activity Code
void loginCall() {
loginViewModel.getUserDetails(editTextUsername.getText().toString().trim(), editTextPassword.getText().toString().trim()).observe(this, new Observer<DATAModel>() {
#Override
public void onChanged(#Nullable DATAModel dataModel) {
if (dataModel != null) {
Userdetails userdetails = dataModel.getUserdetails();
List<ContactTypes> contactTypes = dataModel.getContactTypes();
if (userdetails != null) {
MySharedPreferences.setCustomPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USERDETAILS, userdetails);
MySharedPreferences.setStringPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USER_ID, userdetails.getUserId());
}
if (contactTypes != null) {
MySharedPreferences.setCustomArrayList(LoginActivity.this, Constants.SHAREDPREFERENCE_CONTACTTYPES, contactTypes);
}
Intent i = new Intent(LoginActivity.this, MainActivity.class);
startActivity(i);
finish();
}
}
});
}
Advanced help would be appreciated!
When you call api that time you have to take one live variable which shows your api is in loading mode or not and after success or failure you have to update that variable.
After observe that variable in your activity or fragment class and show or hide your progress.
public class LoginRepository {
private DATAModel dataModel = new DATAModel();
private MutableLiveData<DATAModel> mutableLiveData = new MutableLiveData<>();
private Application application;
private MutableLiveData<Boolean> progressbarObservable;
public LoginRepository(Application application) {
this.application = application;
}
public MutableLiveData<DATAModel> getMutableLiveData(String username, String password) {
// add below line
progressbarObservable.value = true
APIRequest apiRequest = RetrofitRequest.getRetrofit().create(APIRequest.class);
JsonLogin jsonLogin = new JsonLogin(Constants.DEVICE_TYPE, Functions.getDeviceId(application.getApplicationContext()), Constants.APP_VERSION, Constants.API_VERSION, Functions.getTimeStamp(), Functions.getDeviceModel(), Build.VERSION.RELEASE, username, password);
Call<APIResponseLogin> call = apiRequest.getUsersDetails(jsonLogin);
call.enqueue(new Callback<APIResponseLogin>() {
#Override
public void onResponse(Call<APIResponseLogin> call, Response<APIResponseLogin> response) {
// add below line
progressbarObservable.value = false
APIResponseLogin apiResponse = response.body();
if (apiResponse != null && apiResponse.getStatuscode() == 0) {
if (apiResponse.getDataModel() != null) {
dataModel = apiResponse.getDataModel();
mutableLiveData.setValue(dataModel);
}
} else if (apiResponse != null && apiResponse.getStatuscode() == 1) {
Log.v("AAAAAAAAA", apiResponse.getStatusmessage());
}
}
#Override
public void onFailure(Call<APIResponseLogin> call, Throwable t) {
// add below line
progressbarObservable.value = false
Log.v("ErrorResponse", t.getMessage() + " : " + call.request().toString());
}
});
return mutableLiveData;
}
}
Now, observe above variable in activity or fragment and based on that value hide or show your progress bar
public class LoginActivity extends AppCompatActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
observeLogin();
}
#Override
public void onClick(View view)
{
switch (view.getId()) {
case R.id.button_login:
// Do something
loginCall();
}
}
private void observeLogin() {
loginViewModel.progressbarObservable().observe(this, new Observer<Boolean>() {
#Override
public void onChanged(final Boolean progressObserve) {
if(progressObserve){
show your progress
}
else {
hide your progress
}
}
});
}
void loginCall() {
loginViewModel.getUserDetails(editTextUsername.getText().toString().trim(), editTextPassword.getText().toString().trim()).observe(this, new Observer<DATAModel>() {
#Override
public void onChanged(#Nullable DATAModel dataModel) {
if (dataModel != null) {
Userdetails userdetails = dataModel.getUserdetails();
List<ContactTypes> contactTypes = dataModel.getContactTypes();
if (userdetails != null) {
MySharedPreferences.setCustomPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USERDETAILS, userdetails);
MySharedPreferences.setStringPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USER_ID, userdetails.getUserId());
}
if (contactTypes != null) {
MySharedPreferences.setCustomArrayList(LoginActivity.this, Constants.SHAREDPREFERENCE_CONTACTTYPES, contactTypes);
}
Intent i = new Intent(LoginActivity.this, MainActivity.class);
startActivity(i);
finish();
}
}
});
}
}
I find it easier to write my own callback interface in this situation. Just not that this will be done synchronously so all will wait until your api call responds. But in such a case, a progress dialog would be havin the similar effect.
1.Create inteface:
public interface ProgressCallback{
void onDone(String message);
void onFail(String message);
}
Now in your method where you call the API
loginUser(String name, String password, ProgressCallback
progressCallback){
call.enqueue(new Callback<LoginData>() {
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public void onResponse(Call<LoginData> call, Response<LoginData> response) {
progressCallBack.onSuccess(response.message());
}
#Override
public void onFailure(Call<LoginData> call, Throwable t) {
progressCallBack.onFail(t.getMessage());
}
});
Now when you call the method
loginUser("John#doe.com", "applesgravity", new ProgressCallBack() {
#Override
public void onSuccess(String message) {
progressBar.setVisibility(View.INVISIBLE);
}
#Override
public void onFail(String message) {
progressBar.setVisibility(View.INVISIBLE);
}
});
I checked this article but observe the response changes in MainActivity.
Here is my code for LoginRepo
public MutableLiveData<LoginResponseModel> checkLogin(LoginRequestModel loginRequestModel) {
final MutableLiveData<LoginResponseModel> data = new MutableLiveData<>();
Map<String, String> params = new HashMap<>();
params.put("email", loginRequestModel.getEmail());
params.put("password", loginRequestModel.getPassword());
apiService.checkLogin(params)
.enqueue(new Callback<LoginResponseModel>() {
#Override
public void onResponse(Call<LoginResponseModel> call, Response<LoginResponseModel> response) {
if (response.isSuccessful()) {
data.setValue(response.body());
Log.i("Response ", response.body().getMessage());
}
}
#Override
public void onFailure(Call<LoginResponseModel> call, Throwable t) {
data.setValue(null);
}
});
return data;
}
Here is my Code LoginViewModel
public class LoginViewModel extends ViewModel {
public MutableLiveData<String> emailAddress = new MutableLiveData<>();
public MutableLiveData<String> password = new MutableLiveData<>();
Map<String, String> params = new HashMap<>();
LoginRepo loginRepo;
private MutableLiveData<LoginResponseModel> loginResponseModelMutableLiveData;
public LiveData<LoginResponseModel> getUser() {
if (loginResponseModelMutableLiveData == null) {
loginResponseModelMutableLiveData = new MutableLiveData<>();
loginRepo = LoginRepo.getInstance();
}
return loginResponseModelMutableLiveData;
}
//This method is using Retrofit to get the JSON data from URL
private void checkLogin(LoginRequestModel loginRequestModel) {
loginResponseModelMutableLiveData = loginRepo.checkLogin(loginRequestModel);
}
public void onLoginClick(View view) {
LoginRequestModel loginRequestModel = new LoginRequestModel();
loginRequestModel.setEmail(emailAddress.getValue());
loginRequestModel.setPassword(password.getValue());
params.put("email", loginRequestModel.getEmail());
params.put("password", loginRequestModel.getPassword());
checkLogin(loginRequestModel);
}
}
Here is my code for LoginActivity
private LoginViewModel loginViewModel;
private ActivityMainBinding binding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
binding = DataBindingUtil.setContentView(LoginActivity.this, R.layout.activity_main);
binding.setLifecycleOwner(this);
binding.setLoginViewModel(loginViewModel);
loginViewModel.getUser().observe(this, new Observer<LoginResponseModel>() {
#Override
public void onChanged(#Nullable LoginResponseModel loginUser) {
if (loginUser != null) {
binding.lblEmailAnswer.setText(loginUser.getUser().getId());
Toast.makeText(getApplicationContext(), loginUser.getUser().getId(), Toast.LENGTH_SHORT).show();
}
}
});
}
onLoginClick method used in LoginViewModel is using LiveData.
The Response coming from api is okay. But onchange() it is not shown, how to use LiveData using MVVM pattern in simple Login Example. Please help!
Here is what i have tried using your classes just altering retrofit to background thread to wait 5 seconds and then setting the data (you need to confirm the response being successful as you don't change the data if it's failing and hence if the loginResponseModel is null then it will enter the onChanged Method but it won't do anything as you don't have a condition if it is equals to null) here is what i did
in Main Activity -> onCreate() i just created the viewmodel and observed on the mutableLiveData
myViewModel.onLoginClick(null);
myViewModel.simpleModelMutableLiveData.observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
if(s==null)
Log.v("testinggg","test - onChanged --- Null " );
else
Log.v("testinggg","test - onChanged --- s -> "+s );
}
});
Then here is the ViewModel -> in which you will have the MutableLiveData itself named simpleModelMutableLiveData
MutableLiveData<String> simpleModelMutableLiveData;
public LiveData<String> getUser() {
if (simpleModelMutableLiveData == null) {
simpleModelMutableLiveData = new MutableLiveData<>();
}
return simpleModelMutableLiveData;
}
// this method will return Object of MutableLiveData<String> and let the simpleModelMutableLiveData be the returned object
private void checkLogin(String placeholder) {
simpleModelMutableLiveData = MyRepo.checkLogin(placeholder);
}
public void onLoginClick(View view) {
checkLogin("test");
}
and at last the Repo method in which i will return the MutableLiveData and let the simpleModelMutableLiveData to be the return and initiate a background thread using runnable that will wait 5 seconds before it sets the value using a handler (in your case you will need to set the value of the data after enqueue inside the Overridden Methods onResponse and onFailure)
as follows
public static MutableLiveData<String> checkLogin(String test) {
final MutableLiveData<String> data = new MutableLiveData<>();
Runnable r = new Runnable() {
public void run() {
runYourBackgroundTaskHere(data);
}
};
new Thread(r).start();
return data;
}
private static void runYourBackgroundTaskHere(final MutableLiveData<String> data) {
try {
Thread.sleep(5000);
// Handler handler = new Handler();
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
// things to do on the main thread
/* Here i set the data to sss and then null and when
you check the logcat and type the keyword used for
logging which is "testinggg"
you will find it show sss and then null which means
it has entered the onChanged and showed you the log */
data.setValue("sss");
data.setValue(null);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
I'm trying to transform a response from server to my databinding object... I didn't understand too much how i make that with the Transformations of livedata...
I think i'm i need to change few things, but i didnt find what i need to change...
:(
When i call loadSellers(), it dont enter on switchMap function
Can someone help me?
public class SellersViewModel extends BaseViewModel {
private EzGasRepository repository;
private MutableLiveData<List<Seller>> sellers;
#Inject
public SellersViewModel(EzGasRepository repository) {
this.repository = repository;
}
public LiveData<List<Seller>> fetchAllSellers() {
if (sellers == null) {
sellers = new MutableLiveData<>();
loadSellers();
}
return sellers;
}
private LiveData<List<Seller>> loadSellers() {
return Transformations.switchMap(repository.fetchAllSellers(), input -> {
List<Seller> sellerList = new ArrayList<>();
for (SellerResponse sellerResponse: input) {
Seller seller = new Seller();
seller.setSellerName(sellerResponse.getName());
seller.setRating(sellerResponse.getRating());
seller.setProductValue(sellerResponse.getProduct().getValue());
seller.setSellerAddress(sellerResponse.getAddress().getFormattedAddress());
sellerList.add(seller);
}
return sellers;
});
}
}
This is my repository
public LiveData<List<SellerResponse>> fetchAllSellers() {
MutableLiveData<List<SellerResponse>> data = new MutableLiveData<>();
ezGasApi.fetchAllSellers().enqueue(new Callback<List<SellerResponse>>() {
#Override
public void onResponse(Call<List<SellerResponse>> call, Response<List<SellerResponse>> response) {
if(response.isSuccessful()) {
data.setValue(response.body());
} else {
//data.setValue(response.errorBody());
}
}
#Override
public void onFailure(Call<List<SellerResponse>> call, Throwable t) {
//data.setValue(t.getMessage());
Timber.e(t);
}
});
return data;
}
This is my View
viewModel.fetchAllSellers().observe(this, response -> {
});
I am trying to call the web service to fetch the data and storing it into database using following code. I have created a separate class to perform following operation.
Now , the issue is i want to notify my activity when i successfully fetch and store data in database. if some error occurs then i want to show that on UI itself.
somehow i am able to write a code to fetch the data using pagination but not sure how would i notify UI where i can subscribe catch the update related to progress and error if any.
public Flowable<Response> getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.takeUntil(response->response.getSummary().getNext()!=null)
.subscribe(new Subscriber<Response>() {
#Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
#Override
public void onNext(Response response) {
Log.e(TAG, "onNext" );
if(response !=null){
if(response.getFitness()!=null && response.getFitness().size()!=0){
Realm realm = Realm.getDefaultInstance();
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(response.getFitness());
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.i(TAG, "onSuccess , fitness data saved");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.i(TAG, "onError , fitness data failed to save"+error.getMessage() );
}
});
}else{
Log.i(TAG, "onError , no fitness data available" );
}
}else{
Log.i(TAG, "onError , response is null" );
}
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "onError" +t.getMessage());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete");
}
});;
return null;
}
Updated
Created RxBus to propagate events and error on UI
public class RxBus {
private static final RxBus INSTANCE = new RxBus();
private RxBus(){}
private PublishSubject<Object> bus = PublishSubject.create();
public static RxBus getInstance() {
return INSTANCE;
}
public void send(Object o) {
bus.onNext(o);
}
public void error(Throwable e){bus.onError(e);}
public Observable<Object> toObservable() {
return bus;
}
}
in activity
FitnessRepo fitnessRepo = new FitnessRepo();
fitnessRepo.getFitnessData();
RxBus.getInstance().toObservable().subscribe(new Observer<Object>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Object o) {
if(o instanceof RealmList ){
RealmList<Fitness> realmList = (RealmList<Fitness>) o;
Log.e(TAG,"Fitness data size "+realmList.size());
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG,e.getMessage()+ "");
if (e instanceof HttpException) {
ResponseBody body = ((HttpException) e).response().errorBody();
try {
Response response= LoganSquare.parse(body.byteStream(),Response.class);
if(response.getErrors() !=null)
if(response.getErrors().size() > 0)
Log.e(TAG, "Error "+response.getErrors().get(0).getErrors());
} catch (IOException t) {
t.printStackTrace();
}
}
}
#Override
public void onComplete() {
}
});
in a web service call
public void getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
request.setEnd_date("2018-07-01T00:00:00");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) {
Log.e(TAG, " Error ");
return;
}
RxBus.getInstance().send(response.getFitness());
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.copyToRealmOrUpdate(response.getFitness());
});
}
}).subscribe(item ->{
},
error ->{
RxBus.getInstance().error(error);
});
}
I have good news for you! You can delete almost all of that code and just make it generally better as a result!
public void fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).subscribe();
}
This method is on a background thread now and returns void, so the way to emit stuff out of this method would be to use either a PublishSubject (one for success, one for failure) or an EventBus.
private PublishSubject<Object> fitnessResults;
public Observable<Object> observeFitnessResults() {
return fitnessResults;
}
public static class Success {
public Success(List<Fitness> data) {
this.data = data;
}
public List<Fitness> data;
}
public static class Failure {
public Failure(Exception exception) {
this.exception = exception;
}
public Exception exception;
}
public void fetchFitnessData() {
...
fitnessResults.onNext(new Success(data));
} catch(Exception e) {
fitnessResults.onNext(new Failure(e));
And then
errors = observeFitnessResults().ofType(Error.class);
success = observeFitnessResults().ofType(Success.class);
There are different ways to achieve this. I will never handle the subscriptions on my own out of a lifecycle scope as it creates a possibility of memory leak. In your case it seems that both success and failure is bound to the UI so you can simply do this.
public Completable fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
return fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).ignoreElements();
}
At UI level, you can just handle your subscription with both success and failure. In case you need success model can replace Completable with Single or Flowable.
fetchFitnessData.subscrible(Functions.EMPTY_ACTION, Timber::d);
The major advantage with this approach is that you handle your subscription lifecycles.