Comments (11)
We're explicitly avoiding dependency injection for this project. I was considering reading the file into a String
in the Activity and then just passing the presenter the JSON String, but wanted to determine any alternatives.
from nucleus.
If you need "a" Context, I think it's best to dependency inject the application context into your presenter or abstract that dependency away so you'll get a stream from "somewhere".
from nucleus.
App.getInstance()
?
from nucleus.
This is quite strange, and hopefully related. I had been using .getView()
as mentioned, and it worked just fine, and this question was posted as a matter of determining best practices.
However, last Friday a colleague discovered an issue with the application when running in API 25. I found it strange that I had not seen the issue, but after creating a new emulator and installing API 25, I was able to recreate it. I then switched back to the emulator I had been using for development (running API 23), and reinstalled the application. Suddenly, the above described issue appeared. After debugging the issue last Friday and today, it appeared to be a conflict between Firebase and a support library. I found it strange that the issue had suddenly appeared, and that I had not seen it before. Even old revisions that had been working, suddenly were having the same issue.
Fast forward a bit, and I seemed to have resolved the issue described above. However, for some reason, the presenter described in this original issue is no longer functioning as it was. Two issues appear to be present. getView()
occasionally returns null, and the restartableLatestCache
does not seem to be getting a response in some cases. Below is most of the code for the presenter:
/**
* Presenter for the SplashActivity.
* This presenter will parse local data files and signal it's view when completed.
*/
public class SplashActivityPresenter extends RxPresenter<SplashActivity> {
private static final String TAG = "SplashActivityPresenter";
/**
* Request identifier used when parsing the local .json files is needed.
*/
public static final int REQUEST = 1;
/**
* Request identifier used when the local .json files have already been parsed
* and the Realm DB has been populated.
*/
public static final int REQUEST_ALREADY_LOADED = 2;
private Context mContext;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
restartableLatestCache(REQUEST,
() -> getData().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()),
(view, response) -> {
Log.d(TAG, "onResponse: request");
view.onData();
},
(view, err) -> {
Log.d(TAG, "onError: " + err.getMessage());
view.onError();
});
restartableLatestCache(REQUEST_ALREADY_LOADED,
() -> Observable.just(0).subscribeOn(AndroidSchedulers.mainThread()).observeOn(AndroidSchedulers.mainThread()),
(view3, response) -> {
Log.d(TAG, "onResponse: request already loaded");
view3.onData();
},
(view, err) -> {
Log.d(TAG, "onError: " + err.getMessage());
view.onError();
});
}
/**
* Begin a request.
*/
public void request(Context context) {
mContext = context;
Realm.getDefaultInstance().executeTransaction(realm -> {
Bundle bundle = realm.where(Bundle.class).findFirst();
if (bundle != null) {
start(REQUEST_ALREADY_LOADED);
} else {
start(REQUEST);
}
});
}
/**
* Creates an observable to parse the local .json files.
* @return An Observable which will call it's subscriber's onComplete()
* once parsing has been completed.
*/
private Observable getData() {
return Observable.create(subscriber -> {
parseBundles();
parsePhrases();
Log.d(TAG, "getData: onComplete is about to be called.");
subscriber.onComplete();
Log.d(TAG, "getData: onComplete has been called");
});
}
/**
* Parses file and saves the data to Realm.
*/
private void parsePhrases() {
String msg = getView() == null ? "The view is null" : "The view isn't null";
Log.d(TAG, "parsePhrases(): " + msg);
String json = null;
try {
InputStream is = mContext.getAssets().open("someData.json");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
} catch (IOException ex) {
ex.printStackTrace();
return;
}
JSONArray array = null;
try {
array = new JSONArray(json);
} catch (JSONException e) {
e.printStackTrace();
}
if (array != null) {
for (int i = 0; i < array.length(); i++) {
Phrase phrase = new Phrase();
try {
phrase.setVideoPopupInfo(obj.getString("videoPopupInfo"));
} catch (JSONException e) {
Log.e(TAG, "parsePhrases: " + e.getMessage() );
}
//..A note, the above code will often throw and catch the JSONException, but this is ignored as certain JSON objects in the file don't have this property.
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
realm.copyToRealm(phrase);
realm.commitTransaction();
}
}
}
/**
* Parses the file and saves the data to Realm.
*/
private void parseBundles() {
String msg = getView() == null ? "The view is null" : "The view isn't null";
Log.d(TAG, "parseBundles(): " + msg);
String json = null;
try {
InputStream is = mContext.getAssets().open("someDataTwo.json");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
} catch (IOException ex) {
ex.printStackTrace();
}
JSONArray array = null;
try {
array = new JSONArray(json);
} catch (JSONException e) {
e.printStackTrace();
}
if (array != null) {
for (int i = 0; i < array.length(); i++) {
Bundle bundle = new Bundle();
JSONObject obj = null;
try {
obj = array.getJSONObject(i);
} catch (JSONException e) {
e.printStackTrace();
}
//..Parse a number of JSON properties and save to bundle object.
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
realm.copyToRealm(bundle);
realm.commitTransaction();
}
}
}
}
getPresenter().request()
is being called in the Activity's onCreate()
. Moving the call to onResume()
does seem to alleviate the issue of the view being null, however as most example's show this type of call being made in onCreate()
, i'm curious as to what the best option is for this particular case.
Regardless, the issue of the response not being delivered occurs in all cases. As you can see, getData()
creates and returns an observable. It calls the two parsing methods, and then calls subscriber.onComplete()
. I can confirm this due to the two log statements placed before it. However, the restartableLatestCache
for the REQUEST
ID never gets the response. The call to view.onData()
does not occur.
What is the issue here, and how can it be resolved? If this question is better posted as a separate issue, please let me know.
from nucleus.
Can it be because of RxJava version update? Or support library? Support library sometimes break lifecycles.
from nucleus.
@konmik I have all dependencies declared with explicit versions.
RxJava is compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
, support libraries are all 25.3.1
. I have not changed any of these recently.
Something interesting I've noticed: the restartableLatestCache
for the REQUEST_ALREADY_LOADED
seems to work as expected. If the Realm database is populated, start(REQUEST_ALREADY_LOADED)
is called, and the call to view.onData()
there goes through just fine.
from nucleus.
I expanded a few of the lambdas into anonymous inner classes:
restartableFirst(REQUEST,
new Factory<Observable<Object>>() {
@Override
public Observable<Object> create() {
return SplashActivityPresenter.this.getData().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
},
new BiConsumer<SplashActivity, Object>() {
@Override
public void accept(SplashActivity view, Object response) throws Exception {
Log.d(TAG, "onResponse: request");
//view.onData();
}
},
new BiConsumer<SplashActivity, Throwable>() {
@Override
public void accept(SplashActivity view, Throwable err) throws Exception {
Log.d(TAG, "onError: " + err.getMessage());
view.onError();
}
});
The following line is highlighted with a warning:
return SplashActivityPresenter.this.getData().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
Unchecked assignment: 'io.reactivex.Observable' to 'io.reactivex.Observable<java.lang.Object>'. Reason: 'SplashActivityPresenter.this.getData().subscribeOn(Schedulers.io())' has raw type, so result of observeOn is erased
I suppose that may be the issue? Currently, getData()
returns a plain Observable
. Do I need to make it return some type, eg. Observable<T>
?
from nucleus.
I made the following changes:
I modified the below method:
private Observable getData() {
return Observable.create(subscriber -> {
parseBundles();
parsePhrases();
Log.d(TAG, "getData: " + "onComplete about to be called.");
subscriber.onComplete();
Log.d(TAG, "getData: onComplete has been called");
});
}
to:
private Observable<Object> getData() {
return Observable.fromCallable(new Callable<Object>() {
@Override
public Object call() throws Exception {
parseBundles();
parsePhrases();
return 0;
}
});
}
This appears to work. Why might the implementation using Observable.create()
not work as expected?
from nucleus.
@Orbyt https://artemzin.com/blog/rxjava-defer-execution-of-function-via-fromcallable/ ?
(search for others as well). There may be some magic in fromCallable
that helps to make it work.
I suggest you also look into Completable / Single alternatives to 0 and 1 emission Observables, they make code intentions much cleaner.
from nucleus.
@TWiStErRob I have previously come across that page when looking at alternatives to .create()
. Though, I'm still curious as to why .fromCallable()
works in this case.
The reason for returning a 0
was simply return something, the value is not used.
from nucleus.
I need more data on the issue, preferable a minimal repo with reproduced error. Is looks like yet another support library bug for me (I remember they broke view lifecycle inside fragment one day, so this also can be the reason).
from nucleus.
Related Issues (20)
- Best practice for normal case. (get support.) HOT 3
- Wondering about multiple Presenter endpoint calls (Question) HOT 2
- How to get onComplete using restartable methods? HOT 1
- How could i force restartableFirst.onError with mockito? HOT 1
- RxJava latest version not used. HOT 1
- Console error saving file HOT 1
- Activity method called every time HOT 2
- (Question) Don't keep activieties option HOT 1
- Confusion about restartables and the Activity lifecycle HOT 3
- Does Nucleus support native Fragments? HOT 2
- Throwing VerifyError in < API 21 HOT 1
- Nucleus with Dagger 2 HOT 3
- NucleusActionBarActivity with support library 26 HOT 13
- Help with injecting Presenter when using AndroidInjector HOT 3
- Calling setPresenter in OnCreate HOT 2
- clear the delivery cache
- Crashes when starting a new activity using kotlin
- upgrade a new version for androidx, please
- getView() deprecated
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from nucleus.