) {
+ super.observe(owner, Observer { value ->
+ if (pending.compareAndSet(true, false)) {
+ observer.onChanged(value)
+ }
+ })
+ }
+
+ override fun setValue(value: T) {
+ pending.set(true)
+ super.setValue(value)
+ }
+
+}
diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt
new file mode 100644
index 0000000..41ee808
--- /dev/null
+++ b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt
@@ -0,0 +1,49 @@
+package ru.touchin.templates.viewmodel
+
+import android.app.Activity
+import android.arch.lifecycle.LifecycleOwner
+import android.arch.lifecycle.ViewModelProvider
+import android.arch.lifecycle.ViewModelProviders
+import android.support.v4.app.Fragment
+import android.support.v4.app.FragmentActivity
+import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
+
+object LifecycleViewModelProviders {
+
+ /**
+ * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
+ * {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}.
+ *
+ * It uses the given {@link Factory} to instantiate new ViewModels.
+ *
+ * @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity)
+ * @param factory a {@code Factory} to instantiate new ViewModels
+ * @return a ViewModelProvider instance
+ */
+ fun of(lifecycleOwner: LifecycleOwner, factory: ViewModelProvider.Factory = getViewModelFactory(lifecycleOwner)): ViewModelProvider =
+ when (lifecycleOwner) {
+ is ViewController<*, *> -> ViewModelProviders.of(lifecycleOwner.fragment, factory)
+ is Fragment -> ViewModelProviders.of(lifecycleOwner, factory)
+ is FragmentActivity -> ViewModelProviders.of(lifecycleOwner, factory)
+ else -> throw IllegalArgumentException("Not supported LifecycleOwner.")
+ }
+
+ /**
+ * Returns ViewModelProvider.Factory instance from current lifecycleOwner.
+ * Search #ViewModelFactoryProvider are produced according to priorities:
+ * 1. View controller;
+ * 2. Fragment;
+ * 3. Parent fragment recursively;
+ * 4. Activity;
+ * 5. Application.
+ */
+ fun getViewModelFactory(provider: Any): ViewModelProvider.Factory =
+ when (provider) {
+ is ViewModelFactoryProvider -> provider.viewModelFactory
+ is ViewController<*, *> -> getViewModelFactory(provider.fragment)
+ is Fragment -> getViewModelFactory(provider.parentFragment ?: provider.requireActivity())
+ is Activity -> getViewModelFactory(provider.application)
+ else -> throw IllegalArgumentException("View model factory not found.")
+ }
+
+}
diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt
new file mode 100644
index 0000000..8061ba6
--- /dev/null
+++ b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt
@@ -0,0 +1,17 @@
+package ru.touchin.templates.viewmodel
+
+import android.arch.lifecycle.ViewModel
+import android.arch.lifecycle.ViewModelProvider
+
+import javax.inject.Inject
+import javax.inject.Provider
+
+class ViewModelFactory @Inject constructor(
+ private val creators: Map, @JvmSuppressWildcards Provider>
+) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T
+ = (creators[modelClass]?.get() as? T) ?: throw IllegalArgumentException("Unknown model class $modelClass")
+
+}
diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt
new file mode 100644
index 0000000..b1684e2
--- /dev/null
+++ b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt
@@ -0,0 +1,7 @@
+package ru.touchin.templates.viewmodel
+
+interface ViewModelFactoryProvider {
+
+ val viewModelFactory: ViewModelFactory
+
+}
diff --git a/lifecycle-rx/.gitignore b/lifecycle-rx/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/lifecycle-rx/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/lifecycle-rx/build.gradle b/lifecycle-rx/build.gradle
new file mode 100644
index 0000000..0956030
--- /dev/null
+++ b/lifecycle-rx/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion versions.compileSdk
+
+ defaultConfig {
+ minSdkVersion versions.minSdk
+ }
+}
+
+dependencies {
+ api project(":utils")
+ api project(":logging")
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+ implementation "com.android.support:appcompat-v7:$versions.supportLibrary"
+
+ implementation "android.arch.lifecycle:extensions:$versions.lifecycle"
+
+ implementation "io.reactivex.rxjava2:rxjava:$versions.rxJava"
+ implementation "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid"
+}
diff --git a/lifecycle-rx/src/main/AndroidManifest.xml b/lifecycle-rx/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..68016e6
--- /dev/null
+++ b/lifecycle-rx/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/BaseDestroyable.kt
similarity index 97%
rename from utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt
rename to lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/BaseDestroyable.kt
index fafa855..cca546b 100644
--- a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt
+++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/BaseDestroyable.kt
@@ -1,4 +1,4 @@
-package ru.touchin.roboswag.components.utils.destroyable
+package ru.touchin.livedata.destroyable
import io.reactivex.Completable
import io.reactivex.Flowable
diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/Destroyable.kt
similarity index 99%
rename from utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt
rename to lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/Destroyable.kt
index 8ad3012..93dd9b8 100644
--- a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt
+++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/Destroyable.kt
@@ -1,4 +1,4 @@
-package ru.touchin.roboswag.components.utils.destroyable
+package ru.touchin.livedata.destroyable
import io.reactivex.Completable
import io.reactivex.Flowable
diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt
new file mode 100644
index 0000000..afcc376
--- /dev/null
+++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt
@@ -0,0 +1,57 @@
+package ru.touchin.livedata.dispatcher
+
+import android.arch.lifecycle.MutableLiveData
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.disposables.Disposable
+import ru.touchin.livedata.destroyable.BaseDestroyable
+import ru.touchin.livedata.destroyable.Destroyable
+import ru.touchin.livedata.event.CompletableEvent
+import ru.touchin.livedata.event.MaybeEvent
+import ru.touchin.livedata.event.ObservableEvent
+import ru.touchin.livedata.event.SingleEvent
+
+class BaseLiveDataDispatcher(private val destroyable: BaseDestroyable = BaseDestroyable()) : LiveDataDispatcher, Destroyable by destroyable {
+
+ override fun Flowable.dispatchTo(liveData: MutableLiveData>): Disposable {
+ liveData.value = ObservableEvent.Loading(liveData.value?.data)
+ return untilDestroy(
+ { data -> liveData.value = ObservableEvent.Success(data) },
+ { throwable -> liveData.value = ObservableEvent.Error(throwable, liveData.value?.data) },
+ { liveData.value = ObservableEvent.Completed(liveData.value?.data) })
+ }
+
+ override fun Observable.dispatchTo(liveData: MutableLiveData>): Disposable {
+ liveData.value = ObservableEvent.Loading(liveData.value?.data)
+ return untilDestroy(
+ { data -> liveData.value = ObservableEvent.Success(data) },
+ { throwable -> liveData.value = ObservableEvent.Error(throwable, liveData.value?.data) },
+ { liveData.value = ObservableEvent.Completed(liveData.value?.data) })
+ }
+
+ override fun Single.dispatchTo(liveData: MutableLiveData>): Disposable {
+ liveData.value = SingleEvent.Loading(liveData.value?.data)
+ return untilDestroy(
+ { data -> liveData.value = SingleEvent.Success(data) },
+ { throwable -> liveData.value = SingleEvent.Error(throwable, liveData.value?.data) })
+ }
+
+ override fun Completable.dispatchTo(liveData: MutableLiveData): Disposable {
+ liveData.value = CompletableEvent.Loading
+ return untilDestroy(
+ { liveData.value = CompletableEvent.Completed },
+ { throwable -> liveData.value = CompletableEvent.Error(throwable) })
+ }
+
+ override fun Maybe.dispatchTo(liveData: MutableLiveData>): Disposable {
+ liveData.value = MaybeEvent.Loading(liveData.value?.data)
+ return untilDestroy(
+ { data -> liveData.value = MaybeEvent.Success(data) },
+ { throwable -> liveData.value = MaybeEvent.Error(throwable, liveData.value?.data) },
+ { liveData.value = MaybeEvent.Completed(liveData.value?.data) })
+ }
+
+}
diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt
new file mode 100644
index 0000000..e1d7d8f
--- /dev/null
+++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt
@@ -0,0 +1,27 @@
+package ru.touchin.livedata.dispatcher
+
+import android.arch.lifecycle.MutableLiveData
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.disposables.Disposable
+import ru.touchin.livedata.event.CompletableEvent
+import ru.touchin.livedata.event.MaybeEvent
+import ru.touchin.livedata.event.ObservableEvent
+import ru.touchin.livedata.event.SingleEvent
+
+interface LiveDataDispatcher {
+
+ fun Flowable.dispatchTo(liveData: MutableLiveData>): Disposable
+
+ fun Observable.dispatchTo(liveData: MutableLiveData>): Disposable
+
+ fun Single.dispatchTo(liveData: MutableLiveData>): Disposable
+
+ fun Completable.dispatchTo(liveData: MutableLiveData): Disposable
+
+ fun Maybe.dispatchTo(liveData: MutableLiveData>): Disposable
+
+}
diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt
new file mode 100644
index 0000000..d0832ef
--- /dev/null
+++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt
@@ -0,0 +1,14 @@
+package ru.touchin.livedata.event
+
+/**
+ * Event class that emits from [io.reactivex.Completable].
+ */
+sealed class CompletableEvent {
+
+ object Loading: CompletableEvent()
+
+ object Completed: CompletableEvent()
+
+ data class Error(val throwable: Throwable): CompletableEvent()
+
+}
diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt
new file mode 100644
index 0000000..0f88077
--- /dev/null
+++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt
@@ -0,0 +1,16 @@
+package ru.touchin.livedata.event
+
+/**
+ * Event class that emits from [io.reactivex.Maybe].
+ */
+sealed class MaybeEvent(open val data: T?) {
+
+ class Loading(data: T?): MaybeEvent(data)
+
+ class Success(override val data: T): MaybeEvent(data)
+
+ class Error(val throwable: Throwable, data: T?): MaybeEvent(data)
+
+ class Completed(data: T?): MaybeEvent(data)
+
+}
diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt
new file mode 100644
index 0000000..5a6b8e2
--- /dev/null
+++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt
@@ -0,0 +1,16 @@
+package ru.touchin.livedata.event
+
+/**
+ * Event class that emits from [io.reactivex.Observable].
+ */
+sealed class ObservableEvent(open val data: T?) {
+
+ class Loading(data: T? = null): ObservableEvent(data)
+
+ class Success(override val data: T): ObservableEvent(data)
+
+ class Error(val throwable: Throwable, data: T? = null): ObservableEvent(data)
+
+ class Completed(data: T? = null): ObservableEvent(data)
+
+}
diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt
new file mode 100644
index 0000000..8b53f63
--- /dev/null
+++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt
@@ -0,0 +1,14 @@
+package ru.touchin.livedata.event
+
+/**
+ * Event class that emits from [io.reactivex.Single].
+ */
+sealed class SingleEvent(open val data: T?) {
+
+ class Loading(data: T?): SingleEvent(data)
+
+ class Success(override val data: T): SingleEvent(data)
+
+ class Error(val throwable: Throwable, data: T?): SingleEvent(data)
+
+}
diff --git a/lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt b/lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt
new file mode 100644
index 0000000..e27a860
--- /dev/null
+++ b/lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt
@@ -0,0 +1,24 @@
+package ru.touchin.templates.viewmodel
+
+import android.arch.lifecycle.ViewModel
+import android.support.annotation.CallSuper
+import ru.touchin.livedata.dispatcher.BaseLiveDataDispatcher
+import ru.touchin.livedata.dispatcher.LiveDataDispatcher
+import ru.touchin.livedata.destroyable.BaseDestroyable
+import ru.touchin.livedata.destroyable.Destroyable
+
+/**
+ * Base class of ViewModel with [io.reactivex.disposables.Disposable] handling.
+ */
+open class RxViewModel(
+ private val destroyable: BaseDestroyable = BaseDestroyable(),
+ private val liveDataDispatcher: BaseLiveDataDispatcher = BaseLiveDataDispatcher(destroyable)
+) : ViewModel(), Destroyable by destroyable, LiveDataDispatcher by liveDataDispatcher {
+
+ @CallSuper
+ override fun onCleared() {
+ super.onCleared()
+ destroyable.onDestroy()
+ }
+
+}
diff --git a/lifecycle-rx/src/main/res/values/strings.xml b/lifecycle-rx/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b340b3d
--- /dev/null
+++ b/lifecycle-rx/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ lifecycle-rx
+
diff --git a/logging/build.gradle b/logging/build.gradle
index 217c17b..69333c1 100644
--- a/logging/build.gradle
+++ b/logging/build.gradle
@@ -4,7 +4,7 @@ android {
compileSdkVersion versions.compileSdk
defaultConfig {
- minSdkVersion 16
+ minSdkVersion versions.minSdk
}
compileOptions {
diff --git a/navigation/build.gradle b/navigation/build.gradle
index 26d4258..8a95e1c 100644
--- a/navigation/build.gradle
+++ b/navigation/build.gradle
@@ -1,10 +1,11 @@
apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
android {
compileSdkVersion versions.compileSdk
defaultConfig {
- minSdkVersion 16
+ minSdkVersion versions.minSdk
}
compileOptions {
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java
deleted file mode 100644
index 8d68057..0000000
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
- *
- * This file is part of RoboSwag library.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package ru.touchin.roboswag.components.navigation;
-
-import android.support.annotation.NonNull;
-import android.support.v4.app.Fragment;
-
-/**
- * Created by Gavriil Sitnikov on 08/10/2014.
- * Base interface to listen child fragments start.
- * Usually it helps to determine that fragment have showed on screen and we can change {@link android.app.Activity}'s navigation state for example.
- */
-public interface OnFragmentStartedListener {
-
- /**
- * Calls by child fragment (added via {@link android.support.v4.app.FragmentManager}) on it'sstart.
- *
- * @param fragment Child fragment which called this method.
- */
- void onFragmentStarted(@NonNull Fragment fragment);
-
-}
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java
index e2d88f6..090f9c0 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java
+++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java
@@ -29,6 +29,7 @@ import android.view.MenuItem;
import android.view.View;
import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
+import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener;
import ru.touchin.roboswag.components.utils.UiUtils;
/**
@@ -36,7 +37,7 @@ import ru.touchin.roboswag.components.utils.UiUtils;
* Simple realization of one-side {@link ActionBarDrawerToggle}.
*/
public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle
- implements FragmentManager.OnBackStackChangedListener, BaseActivity.OnBackPressedListener {
+ implements FragmentManager.OnBackStackChangedListener, OnBackPressedListener {
@NonNull
private final BaseActivity activity;
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java
index ee39486..2e09da4 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java
+++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java
@@ -25,7 +25,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.ArraySet;
import android.support.v7.app.AppCompatActivity;
-import android.view.MenuItem;
import java.util.Set;
@@ -96,13 +95,9 @@ public abstract class BaseActivity extends AppCompatActivity {
}
@Override
- public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
- if (item.getItemId() == android.R.id.home) {
- onBackPressed();
- return true;
- } else {
- return super.onOptionsItemSelected(item);
- }
+ public boolean onSupportNavigateUp() {
+ onBackPressed();
+ return true;
}
public void addOnBackPressedListener(@NonNull final OnBackPressedListener onBackPressedListener) {
@@ -123,18 +118,4 @@ public abstract class BaseActivity extends AppCompatActivity {
super.onBackPressed();
}
- /*
- * Interface to be implemented for someone who want to intercept device back button pressing event.
- */
- public interface OnBackPressedListener {
-
- /**
- * Calls when user presses device back button.
- *
- * @return True if it is processed by this object.
- */
- boolean onBackPressed();
-
- }
-
}
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java
new file mode 100644
index 0000000..0f052f2
--- /dev/null
+++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java
@@ -0,0 +1,15 @@
+package ru.touchin.roboswag.components.navigation.activities;
+
+/**
+ * Interface to be implemented for someone who want to intercept device back button pressing event.
+ */
+public interface OnBackPressedListener {
+
+ /**
+ * Calls when user presses device back button.
+ *
+ * @return True if it is processed by this object.
+ */
+ boolean onBackPressed();
+
+}
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java
index 065f06b..34e4b32 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java
+++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java
@@ -20,6 +20,8 @@
package ru.touchin.roboswag.components.navigation.fragments;
import android.animation.Animator;
+import android.annotation.SuppressLint;
+import android.arch.lifecycle.Lifecycle;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
@@ -27,8 +29,10 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -41,6 +45,7 @@ import android.widget.FrameLayout;
import java.lang.reflect.Constructor;
+import ru.touchin.roboswag.components.navigation.BuildConfig;
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController;
import ru.touchin.roboswag.core.log.Lc;
import ru.touchin.roboswag.core.log.LcGroup;
@@ -54,24 +59,15 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
* @param Type of {@link FragmentActivity} where fragment could be attached to.
*/
@SuppressWarnings("PMD.TooManyMethods")
-public class ViewControllerFragment extends ViewFragment {
+public class ViewControllerFragment extends Fragment {
private static final String VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA";
private static final String VIEW_CONTROLLER_STATE_EXTRA = "VIEW_CONTROLLER_STATE_EXTRA";
- private static boolean inDebugMode;
private static long acceptableUiCalculationTime = 100;
- /**
- * Enables debugging features like serialization of {@link #getState()} every creation.
- */
- public static void setInDebugMode() {
- inDebugMode = true;
- }
-
/**
* Sets acceptable UI calculation time so there will be warnings in logs if ViewController's inflate/layout actions will take more than that time.
- * Works only if {@link #setInDebugMode()} called.
* It's 100ms by default.
*/
public static void setAcceptableUiCalculationTime(final long acceptableUiCalculationTime) {
@@ -113,6 +109,8 @@ public class ViewControllerFragment>) getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA);
state = savedInstanceState != null
? savedInstanceState.getParcelable(VIEW_CONTROLLER_STATE_EXTRA)
: (getArguments() != null ? getArguments().getParcelable(VIEW_CONTROLLER_STATE_EXTRA) : null);
if (state != null) {
- if (inDebugMode) {
+ if (BuildConfig.DEBUG) {
state = reserialize(state);
}
} else {
@@ -148,43 +146,6 @@ public class ViewControllerFragment constructor = viewControllerClass.getConstructors()[0];
- final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view);
- final long creationTime = inDebugMode ? SystemClock.elapsedRealtime() : 0;
- try {
- switch (constructor.getParameterTypes().length) {
- case 2:
- return (ViewController) constructor.newInstance(creationContext, savedInstanceState);
- case 3:
- return (ViewController) constructor.newInstance(this, creationContext, savedInstanceState);
- default:
- throw new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length);
- }
- } catch (@NonNull final Exception exception) {
- throw new ShouldNotHappenException(exception);
- } finally {
- checkCreationTime(creationTime);
- }
- }
-
- private void checkCreationTime(final long creationTime) {
- if (inDebugMode) {
- final long creationPeriod = SystemClock.elapsedRealtime() - creationTime;
- if (creationPeriod > acceptableUiCalculationTime) {
- LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod);
- }
- }
- }
-
@NonNull
@Override
public final View onCreateView(
@@ -199,7 +160,7 @@ public class ViewControllerFragment constructor = viewControllerClass.getConstructors()[0];
+ final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view);
+ final long creationTime = BuildConfig.DEBUG ? SystemClock.elapsedRealtime() : 0;
+ try {
+ switch (constructor.getParameterTypes().length) {
+ case 2:
+ return (ViewController) constructor.newInstance(creationContext, savedInstanceState);
+ case 3:
+ return (ViewController) constructor.newInstance(this, creationContext, savedInstanceState);
+ default:
+ throw new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length);
+ }
+ } catch (@NonNull final Exception exception) {
+ throw new ShouldNotHappenException(exception);
+ } finally {
+ checkCreationTime(creationTime);
+ }
+ }
+
+ private void checkCreationTime(final long creationTime) {
+ if (BuildConfig.DEBUG) {
+ final long creationPeriod = SystemClock.elapsedRealtime() - creationTime;
+ if (creationPeriod > acceptableUiCalculationTime) {
+ LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod);
+ }
+ }
+ }
+
private static class PlaceholderView extends FrameLayout {
@NonNull
@@ -353,7 +389,7 @@ public class ViewControllerFragment 0) {
+ if (BuildConfig.DEBUG && lastMeasureTime > 0) {
final long layoutTime = SystemClock.uptimeMillis() - lastMeasureTime;
if (layoutTime > acceptableUiCalculationTime) {
LcGroup.UI_METRICS.w("Measure and layout of %s took too much: %dms", tagName, layoutTime);
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java
deleted file mode 100644
index bca1f28..0000000
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
- *
- * This file is part of RoboSwag library.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package ru.touchin.roboswag.components.navigation.fragments;
-
-import android.arch.lifecycle.Lifecycle;
-import android.os.Bundle;
-import android.support.annotation.CallSuper;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import ru.touchin.roboswag.components.navigation.OnFragmentStartedListener;
-import ru.touchin.roboswag.core.log.Lc;
-import ru.touchin.roboswag.core.utils.BiConsumer;
-
-/**
- * Created by Gavriil Sitnikov on 21/10/2015.
- * Non-background fragment that have specific activity as a parent.
- *
- * @param Type of activity which to such fragment could be attached.
- */
-public abstract class ViewFragment extends Fragment implements OnFragmentStartedListener {
-
- private boolean appeared;
-
- /**
- * Returns if fragment have parent fragment.
- *
- * @return Returns true if fragment is in some fragment's children stack.
- */
- public boolean isChildFragment() {
- return getParentFragment() != null;
- }
-
- /**
- * Returns specific activity which to this fragment could be attached.
- *
- * @return Returns parent activity.
- */
- @SuppressWarnings("unchecked")
- @Nullable
- protected final TActivity getBaseActivity() {
- if (getActivity() == null) {
- return null;
- }
-
- try {
- return (TActivity) getActivity();
- } catch (final ClassCastException exception) {
- Lc.assertion(exception);
- return null;
- }
- }
-
- @NonNull
- @Override
- public View onCreateView(@NonNull final LayoutInflater inflater,
- @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- throw new IllegalStateException("Method onCreateView() should be overridden");
- }
-
- @Override
- @CallSuper
- public void onFragmentStarted(@NonNull final Fragment fragment) {
- //do nothing
- }
-
- @Override
- public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- if (getView() == null || getBaseActivity() == null) {
- Lc.assertion("View and activity shouldn't be null");
- }
- }
-
- private void callMethodAfterInstantiation(@NonNull final BiConsumer action) {
- if (getView() == null || getBaseActivity() == null) {
- Lc.assertion("View and activity shouldn't be null");
- return;
- }
- try {
- action.accept(getView(), getBaseActivity());
- } catch (final Exception exception) {
- Lc.assertion(exception);
- }
- }
-
- @Deprecated
- @Override
- public void onStart() {
- super.onStart();
- callMethodAfterInstantiation(this::onStart);
- }
-
- /**
- * Replacement of {@link #onStart} with non null activity as first parameter.
- *
- * @param view Instantiated view.
- * @param activity Activity which fragment attached to.
- */
- @CallSuper
- @SuppressWarnings("RestrictedApi")
- //RestrictedApi: we need isMenuVisible() to check analytics rightly!
- protected void onStart(@NonNull final View view, @NonNull final TActivity activity) {
- if (getParentFragment() instanceof OnFragmentStartedListener) {
- ((OnFragmentStartedListener) getParentFragment()).onFragmentStarted(this);
- } else if (activity instanceof OnFragmentStartedListener) {
- ((OnFragmentStartedListener) activity).onFragmentStarted(this);
- }
- if (!appeared && isMenuVisible()) {
- onAppear(view, activity);
- }
- }
-
- /**
- * Called when fragment is moved in started state and it's {@link #isMenuVisible()} sets to true.
- * Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
- *
- * @param view Instantiated view.
- * @param activity Activity which fragment attached to.
- */
- protected void onAppear(@NonNull final View view, @NonNull final TActivity activity) {
- appeared = true;
- }
-
- @Deprecated
- @Override
- public void onResume() {
- super.onResume();
- callMethodAfterInstantiation(this::onResume);
- }
-
- /**
- * Replacement of {@link #onResume} with non null activity as first parameter.
- *
- * @param view Instantiated view.
- * @param activity Activity which fragment attached to.
- */
- @CallSuper
- protected void onResume(@NonNull final View view, @NonNull final TActivity activity) {
- //do nothing
- }
-
- @Override
- public void setMenuVisibility(final boolean menuVisible) {
- super.setMenuVisibility(menuVisible);
- if (getBaseActivity() != null && getView() != null) {
- final boolean started = getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
- if (!appeared && menuVisible && started) {
- onAppear(getView(), getBaseActivity());
- }
- if (appeared && (!menuVisible || !started)) {
- onDisappear(getView(), getBaseActivity());
- }
- }
- }
-
- @Deprecated
- @Override
- public void onPause() {
- callMethodAfterInstantiation(this::onPause);
- super.onPause();
- }
-
- /**
- * Replacement of {@link #onPause} with non null activity as first parameter.
- *
- * @param view Instantiated view.
- * @param activity Activity which fragment attached to.
- */
- @CallSuper
- protected void onPause(@NonNull final View view, @NonNull final TActivity activity) {
- // do nothing
- }
-
- /**
- * Called when fragment is moved in stopped state or it's {@link #isMenuVisible()} sets to false.
- * Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
- *
- * @param view Instantiated view.
- * @param activity Activity which fragment attached to.
- */
- protected void onDisappear(@NonNull final View view, @NonNull final TActivity activity) {
- appeared = false;
- }
-
- @Deprecated
- @Override
- public void onStop() {
- callMethodAfterInstantiation(this::onStop);
- super.onStop();
- }
-
- /**
- * Replacement of {@link #onStop} with non null activity as first parameter.
- *
- * @param view Instantiated view.
- * @param activity Activity which fragment attached to.
- */
- @CallSuper
- protected void onStop(@NonNull final View view, @NonNull final TActivity activity) {
- if (appeared) {
- onDisappear(view, activity);
- }
- }
-
-}
diff --git a/sample/build.gradle b/sample/build.gradle
index 65044a9..24ac6ff 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -3,14 +3,16 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 27
+ compileSdkVersion versions.compileSdk
+
defaultConfig {
applicationId "ru.touchin.roboswag.components"
- minSdkVersion 16
- targetSdkVersion 27
+ minSdkVersion versions.minSdk
+ targetSdkVersion versions.compileSdk
versionCode 1
versionName "1.0"
}
+
buildTypes {
release {
minifyEnabled false
@@ -21,6 +23,7 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'com.android.support:appcompat-v7:27.1.1'
+
+ implementation "com.android.support:appcompat-v7:$versions.supportLibrary"
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
}
diff --git a/settings.gradle b/settings.gradle
index 9e1f446..c6b0741 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':sample', ':utils', ':logging', ':navigation', ':storable'
+include ':sample', ':utils', ':logging', ':navigation', ':storable', ':api-logansquare', ':lifecycle-common', ':lifecycle-rx'
diff --git a/storable/build.gradle b/storable/build.gradle
index 2a6be21..18cb730 100644
--- a/storable/build.gradle
+++ b/storable/build.gradle
@@ -4,7 +4,7 @@ android {
compileSdkVersion versions.compileSdk
defaultConfig {
- minSdkVersion 16
+ minSdkVersion versions.minSdk
}
compileOptions {
diff --git a/utils/build.gradle b/utils/build.gradle
index 217c17b..69333c1 100644
--- a/utils/build.gradle
+++ b/utils/build.gradle
@@ -4,7 +4,7 @@ android {
compileSdkVersion versions.compileSdk
defaultConfig {
- minSdkVersion 16
+ minSdkVersion versions.minSdk
}
compileOptions {
diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java b/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java
deleted file mode 100644
index c13ac22..0000000
--- a/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package ru.touchin.roboswag.core.utils;
-
-import android.support.annotation.Nullable;
-
-/**
- * A functional interface (callback) that accepts two values (of possibly different types).
- * @param the first value type
- * @param the second value type
- */
-public interface BiConsumer {
-
- /**
- * Performs an operation on the given values.
- * @param t1 the first value
- * @param t2 the second value
- * @throws Exception on error
- */
- void accept(@Nullable T1 t1, @Nullable T2 t2) throws Exception;
-}
diff --git a/utils/src/main/java/ru/touchin/templates/DeviceUtils.java b/utils/src/main/java/ru/touchin/templates/DeviceUtils.java
new file mode 100644
index 0000000..8d8284d
--- /dev/null
+++ b/utils/src/main/java/ru/touchin/templates/DeviceUtils.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2016 Touch Instinct
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package ru.touchin.templates;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Process;
+import android.support.annotation.NonNull;
+import android.telephony.TelephonyManager;
+
+/**
+ * Utility class that is providing common methods related to android device.
+ */
+public final class DeviceUtils {
+
+ /**
+ * Detects active network type.
+ *
+ * @param context Application context
+ * @return Active network type {@link NetworkType}
+ */
+ @NonNull
+ public static NetworkType getNetworkType(@NonNull final Context context) {
+ if (context.checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, Process.myPid(), Process.myUid())
+ != PackageManager.PERMISSION_GRANTED) {
+ return NetworkType.UNKNOWN;
+ }
+ final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ @SuppressLint("MissingPermission") final NetworkInfo info = cm.getActiveNetworkInfo();
+ if (info == null || !info.isConnected()) {
+ return NetworkType.NONE;
+ }
+ if (info.getType() == ConnectivityManager.TYPE_WIFI) {
+ return NetworkType.WI_FI;
+ }
+ if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
+ final int networkType = info.getSubtype();
+ switch (networkType) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ return NetworkType.MOBILE_2G;
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return NetworkType.MOBILE_3G;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ case 19: // NETWORK_TYPE_LTE_CA is hide
+ return NetworkType.MOBILE_LTE;
+ case TelephonyManager.NETWORK_TYPE_UNKNOWN:
+ default:
+ return NetworkType.UNKNOWN;
+ }
+ }
+ return NetworkType.UNKNOWN;
+ }
+
+ /**
+ * Detects if some network connected.
+ *
+ * @param context Application context
+ * @return true if network connected, false otherwise.
+ */
+ public static boolean isNetworkConnected(@NonNull final Context context) {
+ return getNetworkType(context) != NetworkType.NONE;
+ }
+
+ private DeviceUtils() {
+ }
+
+ /**
+ * Available network types.
+ */
+ public enum NetworkType {
+ /**
+ * Mobile 2G network.
+ */
+ MOBILE_2G("2g"),
+ /**
+ * Mobile 3G network.
+ */
+ MOBILE_3G("3g"),
+ /**
+ * Mobile LTE network.
+ */
+ MOBILE_LTE("lte"),
+ /**
+ * Wi-Fi network.
+ */
+ WI_FI("Wi-Fi"),
+ /**
+ * Unknown network type.
+ */
+ UNKNOWN("unknown"),
+ /**
+ * No network.
+ */
+ NONE("none");
+
+ @NonNull
+ private final String name;
+
+ NetworkType(@NonNull final String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return Network type readable name.
+ */
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ }
+
+}