diff --git a/app/dependencies.gradle b/app/dependencies.gradle index 52a288ead..24c889675 100644 --- a/app/dependencies.gradle +++ b/app/dependencies.gradle @@ -15,23 +15,27 @@ dependencies { implementation 'com.coolerfall:android-http-download-manager:1.6.1' // images - def glideVersion = '4.6.1' + def glideVersion = '4.7.1' implementation "com.github.bumptech.glide:glide:${glideVersion}" implementation "com.github.bumptech.glide:okhttp3-integration:${glideVersion}" kapt "com.github.bumptech.glide:compiler:${glideVersion}" // architecture - implementation 'com.evernote:android-job:1.2.5' + implementation 'com.evernote:android-job:1.2.6' implementation 'org.greenrobot:eventbus:3.1.1' // json - implementation 'com.google.code.gson:gson:2.8.4' + implementation 'com.google.code.gson:gson:2.8.5' + implementation 'com.squareup.moshi:moshi:1.6.0' // json:api def retrofitVersion = '2.4.0' implementation "com.squareup.retrofit2:retrofit:${retrofitVersion}" implementation "com.squareup.retrofit2:converter-moshi:${retrofitVersion}" - implementation 'moe.banana:moshi-jsonapi:3.3.1' + + def moshijsonapiVersion = '3.5.0' + implementation "moe.banana:moshi-jsonapi:${moshijsonapiVersion}" + implementation "moe.banana:moshi-jsonapi-retrofit-converter:${moshijsonapiVersion}" // user interface implementation 'com.github.barteksc:android-pdf-viewer:2.8.1' @@ -39,7 +43,7 @@ dependencies { implementation 'com.github.Commit451:bypasses:1.1.0' // video - implementation 'com.devbrackets.android:exomedia:4.1.0' + implementation 'com.devbrackets.android:exomedia:4.2.1' // view binding def butterknifeVersion = '8.8.1' @@ -56,11 +60,11 @@ dependencies { implementation "com.google.android.gms:play-services-gcm:15.0.1" // firebase - implementation "com.crashlytics.sdk.android:crashlytics:2.9.2" - implementation "com.google.firebase:firebase-core:15.0.2" + implementation "com.crashlytics.sdk.android:crashlytics:2.9.3" + implementation "com.google.firebase:firebase-core:16.0.0" // support libraries - def supportVersion = '27.0.2' + def supportVersion = '27.1.1' implementation "com.android.support:support-v4:${supportVersion}" implementation "com.android.support:appcompat-v7:${supportVersion}" implementation "com.android.support:cardview-v7:${supportVersion}" diff --git a/app/src/main/java/de/xikolo/controllers/base/BasePresenterActivity.java b/app/src/main/java/de/xikolo/controllers/base/BasePresenterActivity.java index b72b2775f..39aabf16d 100644 --- a/app/src/main/java/de/xikolo/controllers/base/BasePresenterActivity.java +++ b/app/src/main/java/de/xikolo/controllers/base/BasePresenterActivity.java @@ -38,6 +38,7 @@ protected void onCreate(Bundle savedInstanceState) { private void initLoader() { // LoaderCallbacks as an object, so no hint regarding Loader will be leak to the subclasses. getSupportLoaderManager().initLoader(loaderId(), null, new LoaderManager.LoaderCallbacks

() { + @NonNull @Override public final Loader

onCreateLoader(int id, Bundle args) { if (PRESENTER_LIFECYCLE_LOGGING) Log.i(TAG, "onCreateLoader"); @@ -45,14 +46,15 @@ public final Loader

onCreateLoader(int id, Bundle args) { } @Override - public final void onLoadFinished(Loader

loader, P presenter) { + public final void onLoadFinished(@NonNull Loader

loader, P presenter) { if (PRESENTER_LIFECYCLE_LOGGING) Log.i(TAG, "onLoadFinished"); BasePresenterActivity.this.presenter = presenter; + presenter.onViewAttached(getPresenterView()); onPresenterCreatedOrRestored(presenter); } @Override - public final void onLoaderReset(Loader

loader) { + public final void onLoaderReset(@NonNull Loader

loader) { if (PRESENTER_LIFECYCLE_LOGGING) Log.i(TAG, "onLoaderReset"); BasePresenterActivity.this.presenter = null; onPresenterDestroyed(); @@ -63,12 +65,12 @@ public final void onLoaderReset(Loader

loader) { @Override protected void onStart() { super.onStart(); - presenter.onViewAttached(getPresenterView()); + if (presenter != null) presenter.onViewAttached(getPresenterView()); } @Override protected void onStop() { - presenter.onViewDetached(); + if (presenter != null) presenter.onViewDetached(); super.onStop(); } @@ -77,7 +79,7 @@ protected void onStop() { */ @NonNull protected String tag() { - return TAG; + return this.getClass().getSimpleName(); } /** @@ -88,7 +90,7 @@ protected String tag() { protected abstract PresenterFactory

getPresenterFactory(); /** - * Hook for subclasses that deliver the {@link Presenter} before its View is attached. + * Hook for subclasses that deliver the {@link Presenter} when created or restored. * Can be use to initialize the Presenter or simple hold a reference to it. */ protected void onPresenterCreatedOrRestored(@NonNull P presenter) { diff --git a/app/src/main/java/de/xikolo/controllers/base/BasePresenterFragment.java b/app/src/main/java/de/xikolo/controllers/base/BasePresenterFragment.java index fa772bc98..790f74faf 100644 --- a/app/src/main/java/de/xikolo/controllers/base/BasePresenterFragment.java +++ b/app/src/main/java/de/xikolo/controllers/base/BasePresenterFragment.java @@ -38,6 +38,7 @@ public void onActivityCreated(Bundle savedInstanceState) { private void initLoader() { // LoaderCallbacks as an object, so no hint regarding loader will be leak to the subclasses. getLoaderManager().initLoader(loaderId(), null, new LoaderManager.LoaderCallbacks

() { + @NonNull @Override public final Loader

onCreateLoader(int id, Bundle args) { if (PRESENTER_LIFECYCLE_LOGGING) Log.i(TAG, "onCreateLoader-" + tag()); @@ -45,14 +46,15 @@ public final Loader

onCreateLoader(int id, Bundle args) { } @Override - public final void onLoadFinished(Loader

loader, P presenter) { + public final void onLoadFinished(@NonNull Loader

loader, P presenter) { if (PRESENTER_LIFECYCLE_LOGGING) Log.i(TAG, "onLoadFinished-" + tag()); BasePresenterFragment.this.presenter = presenter; + presenter.onViewAttached(getPresenterView()); onPresenterCreatedOrRestored(presenter); } @Override - public final void onLoaderReset(Loader

loader) { + public final void onLoaderReset(@NonNull Loader

loader) { if (PRESENTER_LIFECYCLE_LOGGING) Log.i(TAG, "onLoaderReset-" + tag()); BasePresenterFragment.this.presenter = null; onPresenterDestroyed(); @@ -63,12 +65,12 @@ public final void onLoaderReset(Loader

loader) { @Override public void onStart() { super.onStart(); - presenter.onViewAttached(getPresenterView()); + if (presenter != null) presenter.onViewAttached(getPresenterView()); } @Override public void onStop() { - presenter.onViewDetached(); + if (presenter != null) presenter.onViewDetached(); super.onStop(); } @@ -82,7 +84,7 @@ public void setTitle(CharSequence title) { */ @NonNull protected String tag() { - return TAG; + return this.getClass().getSimpleName(); } /** @@ -93,7 +95,7 @@ protected String tag() { protected abstract PresenterFactory

getPresenterFactory(); /** - * Hook for subclasses that deliver the {@link Presenter} before its View is attached. + * Hook for subclasses that deliver the {@link Presenter} when created or restored. * Can be use to initialize the Presenter or simple hold a reference to it. */ protected void onPresenterCreatedOrRestored(@NonNull P presenter) { diff --git a/app/src/main/java/de/xikolo/models/base/Sync.java b/app/src/main/java/de/xikolo/models/base/Sync.java index a8397b51f..07300ec28 100644 --- a/app/src/main/java/de/xikolo/models/base/Sync.java +++ b/app/src/main/java/de/xikolo/models/base/Sync.java @@ -81,10 +81,8 @@ public static > Data< public String[] run() { final List ids = new ArrayList<>(); - Realm realm = Realm.getDefaultInstance(); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { + try (Realm realmInstance = Realm.getDefaultInstance()) { + realmInstance.executeTransaction(realm -> { for (T item : items) { if (beforeCommitCallback != null) { beforeCommitCallback.beforeCommit(realm, item.convertToRealmObject()); @@ -116,9 +114,8 @@ public void execute(Realm realm) { results.deleteAllFromRealm(); } else if (Config.DEBUG) Log.d(TAG, "DATA: Deleted 0 local resources from type " + clazz.getSimpleName()); - } - }); - realm.close(); + }); + } return ids.toArray(new String[0]); } @@ -127,14 +124,14 @@ public void execute(Realm realm) { public static class Included> extends Sync { - private Document document; + private Document document; - private Included(Class clazz, Document document) { + private Included(Class clazz, Document document) { super(clazz); this.document = document; } - public static Included with(Class clazz, final Document document) { + public static Included with(Class clazz, final Document document) { return new Included<>(clazz, document); } @@ -151,10 +148,8 @@ public String[] run() { final List ids = new ArrayList<>(); - Realm realm = Realm.getDefaultInstance(); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { + try (Realm realmInstance = Realm.getDefaultInstance()) { + realmInstance.executeTransaction(realm -> { for (Resource resource : document.getIncluded()) { if (resource instanceof RealmAdapter) { RealmAdapter adapter = (RealmAdapter) resource; @@ -192,9 +187,8 @@ public void execute(Realm realm) { results.deleteAllFromRealm(); } else if (Config.DEBUG) Log.d(TAG, "INCLUDED: Deleted 0 local resources from type " + clazz.getSimpleName()); - } - }); - realm.close(); + }); + } return ids.toArray(new String[0]); } diff --git a/app/src/main/java/de/xikolo/network/ApiService.java b/app/src/main/java/de/xikolo/network/ApiService.java index 15ee84f0a..fc96dc373 100644 --- a/app/src/main/java/de/xikolo/network/ApiService.java +++ b/app/src/main/java/de/xikolo/network/ApiService.java @@ -26,6 +26,7 @@ import de.xikolo.models.SubtitleCue; import de.xikolo.models.SubtitleTrack; import de.xikolo.models.Video; +import moe.banana.jsonapi2.JsonApiConverterFactory; import moe.banana.jsonapi2.ResourceAdapterFactory; import okhttp3.Interceptor; import okhttp3.OkHttpClient; diff --git a/app/src/main/java/de/xikolo/network/JsonApiConverterFactory.java b/app/src/main/java/de/xikolo/network/JsonApiConverterFactory.java deleted file mode 100644 index 3a83c7207..000000000 --- a/app/src/main/java/de/xikolo/network/JsonApiConverterFactory.java +++ /dev/null @@ -1,195 +0,0 @@ -package de.xikolo.network; - -import com.squareup.moshi.JsonAdapter; -import com.squareup.moshi.Moshi; -import com.squareup.moshi.Types; - -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -import moe.banana.jsonapi2.ArrayDocument; -import moe.banana.jsonapi2.Document; -import moe.banana.jsonapi2.ObjectDocument; -import moe.banana.jsonapi2.Resource; -import moe.banana.jsonapi2.ResourceIdentifier; -import okhttp3.MediaType; -import okhttp3.RequestBody; -import okhttp3.ResponseBody; -import okio.Buffer; -import retrofit2.Converter; -import retrofit2.Retrofit; - -/** - * See https://gist.github.com/kamikat/baa7d086f932b0dc4fc3f9f02e37a485 - */ - -@SuppressWarnings("unchecked") -public final class JsonApiConverterFactory extends Converter.Factory { - - public static JsonApiConverterFactory create() { - return create(new Moshi.Builder().build()); - } - - public static JsonApiConverterFactory create(Moshi moshi) { - return new JsonApiConverterFactory(moshi, false); - } - - private final Moshi moshi; - private final boolean lenient; - - private JsonApiConverterFactory(Moshi moshi, boolean lenient) { - if (moshi == null) throw new NullPointerException("moshi == null"); - this.moshi = moshi; - this.lenient = lenient; - } - - public JsonApiConverterFactory asLenient() { - return new JsonApiConverterFactory(moshi, true); - } - - private JsonAdapter getAdapterFromType(Type type) { - Class rawType = Types.getRawType(type); - JsonAdapter adapter; - if (rawType.isArray() && ResourceIdentifier.class.isAssignableFrom(rawType.getComponentType())) { - adapter = moshi.adapter(Types.newParameterizedType(Document.class, rawType.getComponentType())); - } else if (List.class.isAssignableFrom(rawType) && type instanceof ParameterizedType) { - Type typeParameter = ((ParameterizedType) type).getActualTypeArguments()[0]; - if (typeParameter instanceof Class && ResourceIdentifier.class.isAssignableFrom((Class) typeParameter)) { - adapter = moshi.adapter(Types.newParameterizedType(Document.class, typeParameter)); - } else { - return null; - } - } else if (ResourceIdentifier.class.isAssignableFrom(rawType)) { - adapter = moshi.adapter(Types.newParameterizedType(Document.class, rawType)); - } else if (Document.class.isAssignableFrom(rawType)) { - adapter = moshi.adapter(Types.newParameterizedType(Document.class, Resource.class)); - } else { - return null; - } - return adapter; - } - - @Override - public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { - JsonAdapter adapter = getAdapterFromType(type); - if (adapter == null) { - return null; - } - if (lenient) { - adapter = adapter.lenient(); - } - return new MoshiResponseBodyConverter<>((JsonAdapter>) adapter, type); - } - - @Override - public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { - JsonAdapter adapter = getAdapterFromType(type); - if (adapter == null) { - return null; - } - if (lenient) { - adapter = adapter.lenient(); - } - return new MoshiRequestBodyConverter<>((JsonAdapter>) adapter, type); - } - - private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); - - private static class MoshiResponseBodyConverter implements Converter { - private final JsonAdapter> adapter; - private final Class rawType; - - MoshiResponseBodyConverter(JsonAdapter> adapter, Type type) { - this.adapter = adapter; - this.rawType = (Class) Types.getRawType(type); - } - - @Override - public R convert(ResponseBody value) throws IOException { - try { - Document document = adapter.fromJson(value.source()); - if (Document.class.isAssignableFrom(rawType)) { - return (R) document; - } else if (List.class.isAssignableFrom(rawType)) { - ArrayDocument arrayDocument = document.asArrayDocument(); - List a; - if (rawType.isAssignableFrom(ArrayList.class)) { - a = new ArrayList(); - } else { - a = (List) rawType.newInstance(); - } - a.addAll(arrayDocument); - return (R) a; - } else if (rawType.isArray()) { - ArrayDocument arrayDocument = document.asArrayDocument(); - Object a = Array.newInstance(rawType.getComponentType(), arrayDocument.size()); - for (int i = 0; i != Array.getLength(a); i++) { - Array.set(a, i, arrayDocument.get(i)); - } - return (R) a; - } else { - return (R) document.asObjectDocument().get(); - } - } catch (InstantiationException e) { - throw new RuntimeException("Cannot find default constructor of [" + rawType.getCanonicalName() + "].", e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access default constructor of [" + rawType.getCanonicalName() + "].", e); - } finally { - value.close(); - } - } - } - - private static class MoshiRequestBodyConverter implements Converter { - - private final JsonAdapter> adapter; - private final Class rawType; - - MoshiRequestBodyConverter(JsonAdapter> adapter, Type type) { - this.adapter = adapter; - this.rawType = (Class) Types.getRawType(type); - } - - @Override - public RequestBody convert(T value) throws IOException { - Document document; - if (Document.class.isAssignableFrom(rawType)) { - document = (Document) value; - } else if (List.class.isAssignableFrom(rawType)) { - ArrayDocument arrayDocument = new ArrayDocument(); - List a = ((List) value); - if (!a.isEmpty() && a.get(0) != null && ((ResourceIdentifier) a.get(0)).getDocument() != null) { - arrayDocument = ((ResourceIdentifier) a.get(0)).getDocument().asArrayDocument(); - } - arrayDocument.addAll(a); - document = arrayDocument; - } else if (rawType.isArray()) { - ArrayDocument arrayDocument = new ArrayDocument(); - if (Array.getLength(value) > 0 && ((ResourceIdentifier) Array.get(value, 0)).getDocument() != null) { - arrayDocument = ((ResourceIdentifier) Array.get(value, 0)).getDocument().asArrayDocument(); - } - for (int i = 0; i != Array.getLength(value); i++) { - arrayDocument.add((ResourceIdentifier) Array.get(value, i)); - } - document = arrayDocument; - } else { - ResourceIdentifier data = ((ResourceIdentifier) value); - ObjectDocument objectDocument = new ObjectDocument(); - if (data.getDocument() != null) { - objectDocument = data.getDocument().asObjectDocument(); - } - objectDocument.set(data); - document = objectDocument; - } - Buffer buffer = new Buffer(); - adapter.toJson(buffer, document); - return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); - } - } - -} diff --git a/app/src/main/java/de/xikolo/presenters/channels/ChannelDetailsPresenter.java b/app/src/main/java/de/xikolo/presenters/channels/ChannelDetailsPresenter.java index 5638cc790..6065ad906 100644 --- a/app/src/main/java/de/xikolo/presenters/channels/ChannelDetailsPresenter.java +++ b/app/src/main/java/de/xikolo/presenters/channels/ChannelDetailsPresenter.java @@ -55,8 +55,10 @@ public void onViewAttached(ChannelDetailsView v) { coursesPromise = getCoursesPromise(); - getViewOrThrow().showContent(); - getViewOrThrow().setupView(channel); + if (isViewAttached()) { + getView().showContent(); + getView().setupView(channel); + } }); } @@ -91,7 +93,7 @@ private RealmResults getCoursesPromise() { courseList.clear(); courseList.add(channel.description, new ArrayList<>()); buildCourseList(); - getViewOrThrow().showCourseList(courseList); + getView().showCourseList(courseList); } }); } diff --git a/app/src/main/java/de/xikolo/presenters/course/CourseDetailsPresenter.java b/app/src/main/java/de/xikolo/presenters/course/CourseDetailsPresenter.java index a43a7b047..321209d97 100644 --- a/app/src/main/java/de/xikolo/presenters/course/CourseDetailsPresenter.java +++ b/app/src/main/java/de/xikolo/presenters/course/CourseDetailsPresenter.java @@ -5,7 +5,6 @@ import de.xikolo.models.Course; import de.xikolo.presenters.base.LoadingStatePresenter; import io.realm.Realm; -import io.realm.RealmChangeListener; import static de.xikolo.jobs.base.RequestJobCallback.ErrorCode.NO_NETWORK; @@ -42,12 +41,11 @@ public void onViewAttached(CourseDetailsView v) { requestCourse(false); } - coursePromise = courseManager.getCourse(courseId, realm, new RealmChangeListener() { - @Override - public void onChange(Course c) { - course = c; - getViewOrThrow().showContent(); - getViewOrThrow().setupView(course); + coursePromise = courseManager.getCourse(courseId, realm, (c) -> { + course = c; + if (isViewAttached()) { + getView().showContent(); + getView().setupView(course); } }); } diff --git a/app/src/main/java/de/xikolo/presenters/course/LearningsPresenter.java b/app/src/main/java/de/xikolo/presenters/course/LearningsPresenter.java index d765a4a6c..91eb926fe 100644 --- a/app/src/main/java/de/xikolo/presenters/course/LearningsPresenter.java +++ b/app/src/main/java/de/xikolo/presenters/course/LearningsPresenter.java @@ -9,7 +9,6 @@ import de.xikolo.models.Section; import de.xikolo.presenters.base.LoadingStatePresenter; import io.realm.Realm; -import io.realm.RealmChangeListener; import io.realm.RealmResults; public class LearningsPresenter extends LoadingStatePresenter { @@ -45,20 +44,16 @@ public void onViewAttached(LearningsView v) { requestSectionListWithItems(false); } - coursePromise = courseManager.getCourse(courseId, realm, new RealmChangeListener() { - @Override - public void onChange(Course course) { - getViewOrThrow().setTitle(course.title); - } + coursePromise = courseManager.getCourse(courseId, realm, (course) -> { + if (isViewAttached()) getView().setTitle(course.title); }); - itemsPromise = itemManager.listAccessibleItemsForCourse(courseId, realm, new RealmChangeListener>() { - @Override - public void onChange(RealmResults items) { - if (items.size() > 0) { - itemList = items; - getViewOrThrow().showContent(); - getViewOrThrow().setupSections(Section.listForCourse(courseId)); + itemsPromise = itemManager.listAccessibleItemsForCourse(courseId, realm, (items) -> { + if (items.size() > 0) { + itemList = items; + if (isViewAttached()) { + getView().showContent(); + getView().setupSections(Section.listForCourse(courseId)); } } }); diff --git a/app/src/main/java/de/xikolo/presenters/course_items/RichTextPresenter.java b/app/src/main/java/de/xikolo/presenters/course_items/RichTextPresenter.java index 4b0fae5ae..735bb963c 100644 --- a/app/src/main/java/de/xikolo/presenters/course_items/RichTextPresenter.java +++ b/app/src/main/java/de/xikolo/presenters/course_items/RichTextPresenter.java @@ -3,7 +3,6 @@ import de.xikolo.config.Config; import de.xikolo.models.RichText; import de.xikolo.utils.LanalyticsUtil; -import io.realm.RealmChangeListener; import io.realm.RealmResults; public class RichTextPresenter extends ItemPresenter { @@ -25,13 +24,12 @@ public void onViewAttached(RichTextView v) { requestItem(false); } - richTextPromise = itemManager.getRichTextForItem(item.contentId, realm, new RealmChangeListener>() { - @Override - public void onChange(RealmResults result) { - if (result.size() > 0) { - richText = realm.copyFromRealm(result.first()); - getViewOrThrow().showContent(); - getViewOrThrow().setupView(item, richText); + richTextPromise = itemManager.getRichTextForItem(item.contentId, realm, (result) -> { + if (result.size() > 0) { + richText = realm.copyFromRealm(result.first()); + if (isViewAttached()) { + getView().showContent(); + getView().setupView(item, richText); } } }); diff --git a/app/src/main/java/de/xikolo/presenters/course_items/VideoPreviewPresenter.java b/app/src/main/java/de/xikolo/presenters/course_items/VideoPreviewPresenter.java index 92fc93f75..0f0a0860c 100644 --- a/app/src/main/java/de/xikolo/presenters/course_items/VideoPreviewPresenter.java +++ b/app/src/main/java/de/xikolo/presenters/course_items/VideoPreviewPresenter.java @@ -5,7 +5,6 @@ import de.xikolo.models.Video; import de.xikolo.utils.CastUtil; import de.xikolo.utils.LanalyticsUtil; -import io.realm.RealmChangeListener; import io.realm.RealmResults; public class VideoPreviewPresenter extends ItemPresenter { @@ -27,13 +26,12 @@ public void onViewAttached(VideoPreviewView v) { requestItem(false); } - videoPromise = itemManager.getVideoForItem(item.contentId, realm, new RealmChangeListener>() { - @Override - public void onChange(RealmResults