From 15cb292af223ea76bf20f257a833c857e79a3867 Mon Sep 17 00:00:00 2001 From: Eric Ampire Date: Mon, 16 Aug 2021 13:02:40 +0200 Subject: [PATCH] Adding app module --- app/build.gradle.kts | 2 + app/src/main/AndroidManifest.xml | 1 + .../android/androidstudycase/app/App.kt | 11 +++++ i18n/.gitignore | 1 + i18n/build.gradle.kts | 11 +++++ i18n/consumer-rules.pro | 0 i18n/proguard-rules.pro | 21 ++++++++ i18n/src/main/AndroidManifest.xml | 4 ++ i18n/src/main/res/values/arrays.xml | 37 ++++++++++++++ i18n/src/main/res/values/strings.xml | 7 +++ settings.gradle.kts | 2 + util/.gitignore | 1 + util/build.gradle.kts | 48 +++++++++++++++++++ util/consumer-rules.pro | 0 util/proguard-rules.pro | 21 ++++++++ util/src/main/AndroidManifest.xml | 5 ++ .../android/beserve/util/DefaultDispatcher.kt | 24 ++++++++++ .../zxconnect/android/beserve/util/Event.kt | 40 ++++++++++++++++ .../zxconnect/android/beserve/util/Result.kt | 40 ++++++++++++++++ .../beserve/util/usecase/CoroutineUseCase.kt | 38 +++++++++++++++ .../beserve/util/usecase/FlowUseCase.kt | 20 ++++++++ .../android/beserve/util/ExampleUnitTest.kt | 17 +++++++ 22 files changed, 351 insertions(+) create mode 100644 app/src/main/java/com/ericampire/android/androidstudycase/app/App.kt create mode 100644 i18n/.gitignore create mode 100644 i18n/build.gradle.kts create mode 100644 i18n/consumer-rules.pro create mode 100644 i18n/proguard-rules.pro create mode 100644 i18n/src/main/AndroidManifest.xml create mode 100644 i18n/src/main/res/values/arrays.xml create mode 100644 i18n/src/main/res/values/strings.xml create mode 100644 util/.gitignore create mode 100644 util/build.gradle.kts create mode 100644 util/consumer-rules.pro create mode 100644 util/proguard-rules.pro create mode 100644 util/src/main/AndroidManifest.xml create mode 100644 util/src/main/java/org/zxconnect/android/beserve/util/DefaultDispatcher.kt create mode 100644 util/src/main/java/org/zxconnect/android/beserve/util/Event.kt create mode 100644 util/src/main/java/org/zxconnect/android/beserve/util/Result.kt create mode 100644 util/src/main/java/org/zxconnect/android/beserve/util/usecase/CoroutineUseCase.kt create mode 100644 util/src/main/java/org/zxconnect/android/beserve/util/usecase/FlowUseCase.kt create mode 100644 util/src/test/java/org/zxconnect/android/beserve/util/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b2eec64..747ea1a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -74,6 +74,8 @@ android { dependencies { + implementation(project(":util")) + implementation(Libs.activity_compose) implementation(Libs.navigation_compose) implementation(Libs.core_ktx) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 66ceb78..99f16f0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:name=".app.App" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.AndroidStudyCase"> diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/App.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/App.kt new file mode 100644 index 0000000..643ba3e --- /dev/null +++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/App.kt @@ -0,0 +1,11 @@ +package com.ericampire.android.androidstudycase.app + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class App : Application() { + override fun onCreate() { + super.onCreate() + } +} \ No newline at end of file diff --git a/i18n/.gitignore b/i18n/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/i18n/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts new file mode 100644 index 0000000..b4ff1ec --- /dev/null +++ b/i18n/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("com.android.library") + id("kotlin-android") +} + +android { + compileSdk = Apps.compileSdk + defaultConfig { + minSdk = Apps.minSdk + } +} \ No newline at end of file diff --git a/i18n/consumer-rules.pro b/i18n/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/i18n/proguard-rules.pro b/i18n/proguard-rules.pro new file mode 100644 index 0000000..ff59496 --- /dev/null +++ b/i18n/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/i18n/src/main/AndroidManifest.xml b/i18n/src/main/AndroidManifest.xml new file mode 100644 index 0000000..557e3e4 --- /dev/null +++ b/i18n/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/i18n/src/main/res/values/arrays.xml b/i18n/src/main/res/values/arrays.xml new file mode 100644 index 0000000..7ce23a6 --- /dev/null +++ b/i18n/src/main/res/values/arrays.xml @@ -0,0 +1,37 @@ + + + + Commandes + Tout + + + + Nouveau fournisseur + Promotions + Commandes + Nouvelle mise à jour + Globale + + + + stores + products + orders + global + default + + + + Être notifié lorsqu\'un nouveau fournisseur est disponible sur Be Served. + Être notifié lorsqu\'il y a de nouvelles promotions. + Être notifié lorsque l\'état de votre commande change. + Être notifié lorsqu\'une nouvelle version de l\'application est disponible. + Autres notifications. + + + + En cours + Refusée + Historique + + \ No newline at end of file diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml new file mode 100644 index 0000000..6702bab --- /dev/null +++ b/i18n/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + + Study case + Be Served Channel + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index edbbb7f..7156dd6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,3 +16,5 @@ dependencyResolutionManagement { } rootProject.name = "android-study-case" include(":app") +include(":util") +include(":i18n") diff --git a/util/.gitignore b/util/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/util/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/util/build.gradle.kts b/util/build.gradle.kts new file mode 100644 index 0000000..cbe6396 --- /dev/null +++ b/util/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + id("com.android.library") + id("kotlin-android") + kotlin("kapt") +} + +android { + compileSdk = Apps.compileSdk + buildToolsVersion = Apps.buildToolsVersion + + defaultConfig { + minSdk = Apps.minSdk + targetSdk = Apps.targetSdk + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +dependencies { + + api(project(":i18n")) + implementation(Libs.core_ktx) + + api(platform(Libs.kotlin_coroutine_bom)) + api(Libs.kotlin_coroutine_core) + + testImplementation(Libs.junit_jupiter_api) + testImplementation(Libs.junit_jupiter_engine) + + testImplementation(Libs.mockk_core) + + api(Libs.hilt_android) + kapt(Libs.hilt_android_compiler) + + api(Libs.timber) +} \ No newline at end of file diff --git a/util/consumer-rules.pro b/util/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/util/proguard-rules.pro b/util/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/util/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/util/src/main/AndroidManifest.xml b/util/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ebdb0bc --- /dev/null +++ b/util/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/util/src/main/java/org/zxconnect/android/beserve/util/DefaultDispatcher.kt b/util/src/main/java/org/zxconnect/android/beserve/util/DefaultDispatcher.kt new file mode 100644 index 0000000..8cab42a --- /dev/null +++ b/util/src/main/java/org/zxconnect/android/beserve/util/DefaultDispatcher.kt @@ -0,0 +1,24 @@ +package org.zxconnect.android.beserve.util + +import javax.inject.Qualifier + + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class DefaultDispatcher + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class IoDispatcher + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class MainDispatcher + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class MainImmediateDispatcher + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class ApplicationScope \ No newline at end of file diff --git a/util/src/main/java/org/zxconnect/android/beserve/util/Event.kt b/util/src/main/java/org/zxconnect/android/beserve/util/Event.kt new file mode 100644 index 0000000..075be71 --- /dev/null +++ b/util/src/main/java/org/zxconnect/android/beserve/util/Event.kt @@ -0,0 +1,40 @@ +package org.zxconnect.android.beserve.util + +import androidx.lifecycle.Observer + +open class Event(private val content: T) { + + var hasBeenHandled = false + private set // Allow external read but not write + + /** + * Returns the content and prevents its use again. + */ + fun getContentIfNotHandled(): T? { + return if (hasBeenHandled) { + null + } else { + hasBeenHandled = true + content + } + } + + /** + * Returns the content, even if it's already been handled. + */ + fun peekContent(): T = content +} + +/** + * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has + * already been handled. + * + * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. + */ +class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> { + override fun onChanged(event: Event?) { + event?.getContentIfNotHandled()?.let { value -> + onEventUnhandledContent(value) + } + } +} \ No newline at end of file diff --git a/util/src/main/java/org/zxconnect/android/beserve/util/Result.kt b/util/src/main/java/org/zxconnect/android/beserve/util/Result.kt new file mode 100644 index 0000000..7614a19 --- /dev/null +++ b/util/src/main/java/org/zxconnect/android/beserve/util/Result.kt @@ -0,0 +1,40 @@ +package org.zxconnect.android.beserve.util + +import androidx.lifecycle.MutableLiveData + +sealed class Result { + + data class Success(val data: T) : Result() + data class Error(val exception: Exception) : Result() + object Loading : Result() + + override fun toString(): String { + return when (this) { + is Success<*> -> "Success[data=$data]" + is Error -> "Error[exception=$exception]" + Loading -> "Loading" + } + } +} + +/** + * `true` if [Result] is of type [Success] & hoxlds non-null [Success.data]. + */ +val Result<*>.succeeded + get() = this is Result.Success && data != null + +fun Result.successOr(fallback: T): T { + return (this as? Result.Success)?.data ?: fallback +} + +val Result.data: T? + get() = (this as? Result.Success)?.data + +/** + * Updates value of [liveData] if [Result] is of type [Success] + */ +inline fun Result.updateOnSuccess(liveData: MutableLiveData) { + if (this is Result.Success) { + liveData.value = data + } +} \ No newline at end of file diff --git a/util/src/main/java/org/zxconnect/android/beserve/util/usecase/CoroutineUseCase.kt b/util/src/main/java/org/zxconnect/android/beserve/util/usecase/CoroutineUseCase.kt new file mode 100644 index 0000000..dd5ea05 --- /dev/null +++ b/util/src/main/java/org/zxconnect/android/beserve/util/usecase/CoroutineUseCase.kt @@ -0,0 +1,38 @@ +package org.zxconnect.android.beserve.util.usecase + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import org.zxconnect.android.beserve.util.Result +import timber.log.Timber + + +abstract class CoroutineUseCase(private val coroutineDispatcher: CoroutineDispatcher) { + + /** Executes the use case asynchronously and returns a [Result]. + * + * @return a [Result]. + * + * @param parameters the input parameters to run the use case with + */ + suspend operator fun invoke(parameters: P): Result { + return try { + // Moving all use case's executions to the injected dispatcher + // In production code, this is usually the Default dispatcher (background thread) + // In tests, this becomes a TestCoroutineDispatcher + withContext(coroutineDispatcher) { + execute(parameters).let { + Result.Success(it) + } + } + } catch (e: Exception) { + Timber.d(e) + Result.Error(e) + } + } + + /** + * Override this to set the code to be executed. + */ + @Throws(RuntimeException::class) + protected abstract suspend fun execute(parameters: P): R +} \ No newline at end of file diff --git a/util/src/main/java/org/zxconnect/android/beserve/util/usecase/FlowUseCase.kt b/util/src/main/java/org/zxconnect/android/beserve/util/usecase/FlowUseCase.kt new file mode 100644 index 0000000..6822028 --- /dev/null +++ b/util/src/main/java/org/zxconnect/android/beserve/util/usecase/FlowUseCase.kt @@ -0,0 +1,20 @@ +package org.zxconnect.android.beserve.util.usecase + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flowOn +import org.zxconnect.android.beserve.util.Result + +/** + * Executes business logic in its execute method and keep posting updates to the result as + * [Result]. + * Handling an exception (emit [Result.Error] to the result) is the subclasses's responsibility. + */ +abstract class FlowUseCase(private val coroutineDispatcher: CoroutineDispatcher) { + operator fun invoke(parameters: P): Flow> = execute(parameters) + .catch { e -> emit(Result.Error(Exception(e))) } + .flowOn(coroutineDispatcher) + + protected abstract fun execute(parameters: P): Flow> +} diff --git a/util/src/test/java/org/zxconnect/android/beserve/util/ExampleUnitTest.kt b/util/src/test/java/org/zxconnect/android/beserve/util/ExampleUnitTest.kt new file mode 100644 index 0000000..09669d1 --- /dev/null +++ b/util/src/test/java/org/zxconnect/android/beserve/util/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package org.zxconnect.android.beserve.util + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file