├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ ├── styles.xml │ │ │ └── dimens.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── layout │ │ │ ├── fragment_main.xml │ │ │ └── activity_main.xml │ │ ├── navigation │ │ │ └── nav_graph.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ └── com │ │ │ └── enginebai │ │ │ └── project │ │ │ ├── di │ │ │ ├── AppModule.kt │ │ │ ├── UtilsModule.kt │ │ │ └── NetworkModule.kt │ │ │ ├── AppContext.kt │ │ │ ├── MainActivity.kt │ │ │ └── utils │ │ │ └── ExceptionHandler.kt │ │ └── AndroidManifest.xml ├── build.gradle.kts └── proguard-rules.pro ├── settings.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── encodings.xml ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── kotlinScripting.xml ├── compiler.xml ├── runConfigurations.xml ├── misc.xml └── jarRepositories.xml ├── .circleci └── config.yml ├── LICENSE ├── gradle.properties ├── EXTENSIONS.md ├── gradlew.bat ├── gradlew ├── .gitignore └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Base 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include(":app") 2 | // TODO: 1. change your project name 3 | rootProject.name="AndroidBase" 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enginebai/AndroidBase/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/kotlinScripting.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | android: circleci/android@0.2.0 5 | 6 | jobs: 7 | build: 8 | executor: android/android 9 | 10 | steps: 11 | - checkout 12 | - run: 13 | command: ./gradlew build 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 09 12:15:45 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/enginebai/project/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.enginebai.project.di 2 | 3 | import com.enginebai.project.utils.ExceptionHandler 4 | import com.google.gson.Gson 5 | import org.koin.dsl.module 6 | 7 | val appModule = module { 8 | single { Gson() } 9 | single(createdAtStart = true) { ExceptionHandler() } 10 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/enginebai/project/AppContext.kt: -------------------------------------------------------------------------------- 1 | package com.enginebai.project 2 | 3 | import com.enginebai.base.BaseApplication 4 | import com.enginebai.project.di.networkModule 5 | import com.enginebai.project.di.appModule 6 | import com.enginebai.project.di.logModule 7 | 8 | class AppContext : BaseApplication() { 9 | override fun defineDependencies() = listOf(networkModule, logModule, appModule) 10 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32dp 4 | 16dp 5 | 12dp 6 | 8dp 7 | 4dp 8 | 9 | 20sp 10 | 18sp 11 | 16sp 12 | 14sp 13 | 12sp 14 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | commonPlugins.forEach { id(it) } 4 | id("androidx.navigation.safeargs.kotlin") 5 | } 6 | 7 | //apply(from = "../dependencies.gradle.kts") 8 | configAndroid() 9 | importCommonDependencies() 10 | 11 | android { 12 | defaultConfig { 13 | applicationId = Versions.App.id 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation(Dependencies.androidBase) 19 | implementation(Dependencies.Navigation.fragmentKtx) 20 | implementation(Dependencies.Navigation.uiKtx) 21 | 22 | implementation(Dependencies.okhttp) 23 | implementation(Dependencies.okhttpLogging) 24 | implementation(Dependencies.Retrofit.core) 25 | implementation(Dependencies.Retrofit.gsonConverter) 26 | implementation(Dependencies.Retrofit.rxJavaAdapter) 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/enginebai/project/di/UtilsModule.kt: -------------------------------------------------------------------------------- 1 | package com.enginebai.project.di 2 | 3 | import com.enginebai.base.BuildConfig 4 | import com.enginebai.base.utils.logging.TimberLoggerDebugTree 5 | import com.orhanobut.logger.AndroidLogAdapter 6 | import com.orhanobut.logger.FormatStrategy 7 | import com.orhanobut.logger.PrettyFormatStrategy 8 | import org.koin.dsl.module 9 | import timber.log.Timber 10 | 11 | const val LOG_TAG = "enginebai" 12 | 13 | val logModule = module { 14 | single { (formatStrategy: FormatStrategy) -> 15 | object : AndroidLogAdapter(formatStrategy) { 16 | override fun isLoggable(priority: Int, tag: String?): Boolean { 17 | return BuildConfig.DEBUG && tag == LOG_TAG 18 | } 19 | } 20 | } 21 | 22 | single { 23 | PrettyFormatStrategy.newBuilder() 24 | .tag(LOG_TAG) 25 | .methodCount(3) 26 | .methodOffset(5) // avoid timber internal stack track 27 | .build() 28 | } 29 | 30 | single { TimberLoggerDebugTree() } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Engine Bai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Android Lint 12 | 13 | 14 | 15 | 16 | Spelling 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 34 | 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/enginebai/project/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.enginebai.project 2 | 3 | import com.enginebai.base.view.BaseActivity 4 | import com.enginebai.project.utils.ExceptionHandler 5 | import io.reactivex.android.schedulers.AndroidSchedulers 6 | import io.reactivex.disposables.Disposable 7 | import org.koin.android.ext.android.inject 8 | import java.util.concurrent.TimeUnit 9 | 10 | // TODO: 4. rename the package for your project 11 | class MainActivity : BaseActivity() { 12 | 13 | private val exceptionHandler: ExceptionHandler by inject() 14 | private var exceptionHandlerDisposable: Disposable? = null 15 | 16 | override fun onStart() { 17 | super.onStart() 18 | // You will handle the error message at single activity. 19 | if (null == exceptionHandlerDisposable || false == exceptionHandlerDisposable?.isDisposed) { 20 | exceptionHandlerDisposable = exceptionHandler.errorMessageToDisplay 21 | .filter { it.isNotBlank() } 22 | .throttleFirst(2, TimeUnit.SECONDS) 23 | .observeOn(AndroidSchedulers.mainThread()) 24 | .doOnNext { handleErrorMessage(it) } 25 | .subscribe() 26 | } 27 | } 28 | 29 | override fun onStop() { 30 | exceptionHandlerDisposable?.dispose() 31 | super.onStop() 32 | } 33 | 34 | override fun getLayoutId() = R.layout.activity_main 35 | 36 | override fun handleErrorMessage(message: String) { 37 | TODO("Not yet implemented") 38 | } 39 | } -------------------------------------------------------------------------------- /EXTENSIONS.md: -------------------------------------------------------------------------------- 1 | # Useful Extension Functions 2 | ## Coroutine 3 | ```kotlin 4 | suspend fun fetchApi(): User { ... } 5 | 6 | // Retry with exponential backoff 7 | retry { 8 | fetchApi() 9 | } 10 | 11 | val controller = CoroutineRunningController() 12 | controller.cancelPreviousThenRun(fetchApi()) 13 | controller.queueTask(fetchApi()) 14 | controller.joinPreviousOrRun(fetchApi()) 15 | ``` 16 | 17 | ## View 18 | ```kotlin 19 | // Prevent multiple/duplicate click 20 | View.debounceClick() { ... } 21 | 22 | // Visibility 23 | View.visible() 24 | View.invisible() 25 | View.gone() 26 | View.showIf { ... } 27 | View.hideIf { ... } 28 | View.goneIf { ... } 29 | 30 | // Keyboard 31 | View.showKeyboard() 32 | View.hideKeyboard() 33 | 34 | // Dimension 35 | View.px2dp() 36 | View.dp2px() 37 | View.px2sp() 38 | View.sp2px() 39 | 40 | // Resources 41 | View.getColor(R.color.colorPrimary) 42 | View.getDrawable(R.drawable.ic_launcher) 43 | ``` 44 | 45 | ## EditText 46 | ```kotlin 47 | EditText.textChanged { s -> ... } 48 | EditText.textChanged().doOnNext { s -> ... }.subscribe() 49 | EditText.validate(errorMessage = "The name should not be empty") { !it.isNullOrBlank() } 50 | EditText.validateEmail { ... } 51 | EditText.validateEmail("It's not valid email") 52 | EditText.showPassword() 53 | EditText.hidePassword() 54 | ``` 55 | 56 | ## TextView 57 | ```kotlin 58 | // Set view gone while text is null or empty. 59 | TextView.setTextWithExistence("1234") 60 | 61 | // Set view invislble while text is null or empty. 62 | TextView.setTextWithVisibility("5678") 63 | ``` 64 | 65 | ## String 66 | ```kotlin 67 | String?.isValidEmail(): Boolean 68 | ``` 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/enginebai/project/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.enginebai.project.di 2 | 3 | import com.enginebai.base.extensions.EnumGsonSerializedNameConverterFactory 4 | import com.enginebai.base.extensions.EnumHasValueConverterFactory 5 | import com.enginebai.project.BuildConfig 6 | import okhttp3.OkHttpClient 7 | import okhttp3.Protocol 8 | import okhttp3.logging.HttpLoggingInterceptor 9 | import org.koin.dsl.module 10 | import retrofit2.Retrofit 11 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 12 | import retrofit2.converter.gson.GsonConverterFactory 13 | 14 | val networkModule = module { 15 | single { 16 | HttpLoggingInterceptor().apply { 17 | level = if (BuildConfig.DEBUG) 18 | HttpLoggingInterceptor.Level.BODY 19 | else 20 | HttpLoggingInterceptor.Level.NONE 21 | } 22 | } 23 | single { 24 | val builder = OkHttpClient.Builder() 25 | builder.addInterceptor(get()) 26 | builder.protocols(listOf(Protocol.HTTP_1_1, Protocol.HTTP_2)) 27 | builder.build() 28 | } 29 | 30 | single { GsonConverterFactory.create() } 31 | single { EnumGsonSerializedNameConverterFactory } 32 | single { EnumHasValueConverterFactory } 33 | single { RxJava2CallAdapterFactory.create() } 34 | single { 35 | // TODO: 5. Set your API base url 36 | Retrofit.Builder() 37 | .baseUrl("") 38 | .addCallAdapterFactory(get()) 39 | .addConverterFactory(get()) 40 | .addConverterFactory(get()) 41 | .client(get()) 42 | .build() 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/enginebai/project/utils/ExceptionHandler.kt: -------------------------------------------------------------------------------- 1 | package com.enginebai.project.utils 2 | 3 | import io.reactivex.exceptions.OnErrorNotImplementedException 4 | import io.reactivex.exceptions.UndeliverableException 5 | import io.reactivex.functions.Consumer 6 | import io.reactivex.plugins.RxJavaPlugins 7 | import io.reactivex.subjects.PublishSubject 8 | import retrofit2.HttpException 9 | import timber.log.Timber 10 | import java.net.ConnectException 11 | import java.net.SocketException 12 | import java.net.SocketTimeoutException 13 | import java.net.UnknownHostException 14 | 15 | /** 16 | * The exception handler for the application, it will handle RxJava exception automatically, 17 | * and for non-RxJava you call `exceptionHandler.accept(e)`, and add the new branch condition 18 | * for your custom exception you want to handle. 19 | * 20 | * If you want to just display error message, call `errorMessageToDisplay.onNext(error message)`. 21 | * If you want to propagate the exception, call `handledException.onNext(e)`. 22 | * 23 | * To display the error message, you need to subscribe the `errorMessageToDisplay` subject. 24 | * To handle the propagated exception, you need to subscribe the `handledException` subject. 25 | */ 26 | class ExceptionHandler : Consumer { 27 | 28 | // Emit strings if you handle the exception and just display error message. 29 | val errorMessageToDisplay = PublishSubject.create() 30 | // Emit the exception if you'd like to propagate the exception and handle in other modules or layers 31 | val handledException = PublishSubject.create() 32 | 33 | init { 34 | RxJavaPlugins.setErrorHandler(this) 35 | } 36 | 37 | override fun accept(t: Throwable) { 38 | val cause = parseCause(t) 39 | Timber.d("Handle exception: $cause from $t") 40 | when (cause) { 41 | is SocketTimeoutException, is ConnectException, is UnknownHostException, is SocketException -> { 42 | val errorMessage = "Network failed: $cause" 43 | Timber.w(errorMessage) 44 | errorMessageToDisplay.onNext(errorMessage) 45 | } 46 | is HttpException -> { 47 | val url = cause.response()?.raw()?.request?.url 48 | errorMessageToDisplay.onNext("HTTP ${cause.code()} of $url") 49 | } 50 | is ApiException -> { 51 | handledException.onNext(cause) 52 | } 53 | // TODO: add case to handle your custom exception 54 | else -> { 55 | // For the case you can't catch or handle 56 | Timber.e(cause) 57 | throw cause 58 | } 59 | } 60 | } 61 | 62 | private fun parseCause(t: Throwable): Throwable { 63 | when (t) { 64 | is OnErrorNotImplementedException, is UndeliverableException, is RuntimeException -> { 65 | t.cause?.run { return this } ?: throw ParseCauseFailException(t) 66 | } 67 | } 68 | return t 69 | } 70 | } 71 | 72 | class ParseCauseFailException(t: Throwable) : RuntimeException(t) 73 | class ApiException(t: Throwable) : RuntimeException(t) 74 | 75 | // TODO: you can create the custom exception for your logic 76 | // Such as UsernameNotFoundException(), PasswordIncorrectException() -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 21 | 22 | 23 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | xmlns:android 32 | 33 | ^$ 34 | 35 | 36 | 37 |
38 |
39 | 40 | 41 | 42 | xmlns:.* 43 | 44 | ^$ 45 | 46 | 47 | BY_NAME 48 | 49 |
50 |
51 | 52 | 53 | 54 | .*:id 55 | 56 | http://schemas.android.com/apk/res/android 57 | 58 | 59 | 60 |
61 |
62 | 63 | 64 | 65 | .*:name 66 | 67 | http://schemas.android.com/apk/res/android 68 | 69 | 70 | 71 |
72 |
73 | 74 | 75 | 76 | name 77 | 78 | ^$ 79 | 80 | 81 | 82 |
83 |
84 | 85 | 86 | 87 | style 88 | 89 | ^$ 90 | 91 | 92 | 93 |
94 |
95 | 96 | 97 | 98 | .* 99 | 100 | ^$ 101 | 102 | 103 | BY_NAME 104 | 105 |
106 |
107 | 108 | 109 | 110 | .* 111 | 112 | http://schemas.android.com/apk/res/android 113 | 114 | 115 | ANDROID_ATTRIBUTE_ORDER 116 | 117 |
118 |
119 | 120 | 121 | 122 | .* 123 | 124 | .* 125 | 126 | 127 | BY_NAME 128 | 129 |
130 |
131 |
132 |
133 |
134 |
-------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | ### Android template 16 | # Built application files 17 | *.apk 18 | *.ap_ 19 | *.aab 20 | 21 | # Files for the ART/Dalvik VM 22 | *.dex 23 | 24 | # Java class files 25 | *.class 26 | 27 | # Generated files 28 | bin/ 29 | gen/ 30 | out/ 31 | release/ 32 | 33 | # Gradle files 34 | .gradle/ 35 | build/ 36 | 37 | # Local configuration file (sdk path, etc) 38 | local.properties 39 | 40 | # Proguard folder generated by Eclipse 41 | proguard/ 42 | 43 | # Log Files 44 | *.log 45 | 46 | # Android Studio Navigation editor temp files 47 | .navigation/ 48 | 49 | # Android Studio captures folder 50 | captures/ 51 | 52 | # IntelliJ 53 | .idea/workspace.xml 54 | .idea/tasks.xml 55 | .idea/gradle.xml 56 | .idea/assetWizardSettings.xml 57 | .idea/dictionaries 58 | .idea/libraries 59 | # Android Studio 3 in .gitignore file. 60 | .idea/caches 61 | .idea/modules.xml 62 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 63 | .idea/navEditor.xml 64 | 65 | # Keystore files 66 | # Uncomment the following lines if you do not want to check your keystore files in. 67 | #*.jks 68 | #*.keystore 69 | 70 | # External native build folder generated in Android Studio 2.2 and later 71 | 72 | # Google Services (e.g. APIs or Firebase) 73 | # google-services.json 74 | 75 | # Freeline 76 | freeline.py 77 | freeline/ 78 | freeline_project_description.json 79 | 80 | # fastlane 81 | fastlane/report.xml 82 | fastlane/Preview.html 83 | fastlane/screenshots 84 | fastlane/test_output 85 | fastlane/readme.md 86 | 87 | # Version control 88 | vcs.xml 89 | 90 | # lint 91 | lint/intermediates/ 92 | lint/generated/ 93 | lint/outputs/ 94 | lint/tmp/ 95 | # lint/reports/ 96 | 97 | ### JetBrains template 98 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 99 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 100 | 101 | # User-specific stuff 102 | .idea/**/workspace.xml 103 | .idea/**/tasks.xml 104 | .idea/**/usage.statistics.xml 105 | .idea/**/dictionaries 106 | .idea/**/shelf 107 | 108 | # Generated files 109 | .idea/**/contentModel.xml 110 | 111 | # Sensitive or high-churn files 112 | .idea/**/dataSources/ 113 | .idea/**/dataSources.ids 114 | .idea/**/dataSources.local.xml 115 | .idea/**/sqlDataSources.xml 116 | .idea/**/dynamic.xml 117 | .idea/**/uiDesigner.xml 118 | .idea/**/dbnavigator.xml 119 | 120 | # Gradle 121 | .idea/**/gradle.xml 122 | .idea/**/libraries 123 | 124 | # Gradle and Maven with auto-import 125 | # When using Gradle or Maven with auto-import, you should exclude module files, 126 | # since they will be recreated, and may cause churn. Uncomment if using 127 | # auto-import. 128 | # .idea/modules.xml 129 | # .idea/*.iml 130 | # .idea/modules 131 | # *.iml 132 | # *.ipr 133 | 134 | # CMake 135 | cmake-build-*/ 136 | 137 | # Mongo Explorer plugin 138 | .idea/**/mongoSettings.xml 139 | 140 | # File-based project format 141 | *.iws 142 | 143 | # IntelliJ 144 | 145 | # mpeltonen/sbt-idea plugin 146 | .idea_modules/ 147 | 148 | # JIRA plugin 149 | atlassian-ide-plugin.xml 150 | 151 | # Cursive Clojure plugin 152 | .idea/replstate.xml 153 | 154 | # Crashlytics plugin (for Android Studio and IntelliJ) 155 | com_crashlytics_export_strings.xml 156 | crashlytics.properties 157 | crashlytics-build.properties 158 | fabric.properties 159 | 160 | # Editor-based Rest Client 161 | .idea/httpRequests 162 | 163 | # Android studio 3.1+ serialized cache file 164 | .idea/caches/build_file_checksums.ser 165 | 166 | ### Kotlin template 167 | # Compiled class file 168 | 169 | # Log file 170 | 171 | # BlueJ files 172 | *.ctxt 173 | 174 | # Mobile Tools for Java (J2ME) 175 | .mtj.tmp/ 176 | 177 | # Package Files # 178 | *.jar 179 | !gradle/wrapper/gradle-wrapper.jar 180 | *.war 181 | *.nar 182 | *.ear 183 | *.zip 184 | *.tar.gz 185 | *.rar 186 | 187 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 188 | hs_err_pid* 189 | ### Kotlin template 190 | # Compiled class file 191 | 192 | # Log file 193 | 194 | # BlueJ files 195 | 196 | # Mobile Tools for Java (J2ME) 197 | 198 | # Package Files # 199 | 200 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 201 | 202 | ### JetBrains template 203 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 204 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 205 | 206 | # User-specific stuff 207 | 208 | # Generated files 209 | 210 | # Sensitive or high-churn files 211 | 212 | # Gradle 213 | 214 | # Gradle and Maven with auto-import 215 | # When using Gradle or Maven with auto-import, you should exclude module files, 216 | # since they will be recreated, and may cause churn. Uncomment if using 217 | # auto-import. 218 | # .idea/modules.xml 219 | # .idea/*.iml 220 | # .idea/modules 221 | # *.iml 222 | # *.ipr 223 | 224 | # CMake 225 | 226 | # Mongo Explorer plugin 227 | 228 | # File-based project format 229 | 230 | # IntelliJ 231 | 232 | # mpeltonen/sbt-idea plugin 233 | 234 | # JIRA plugin 235 | 236 | # Cursive Clojure plugin 237 | 238 | # Crashlytics plugin (for Android Studio and IntelliJ) 239 | 240 | # Editor-based Rest Client 241 | 242 | # Android studio 3.1+ serialized cache file 243 | 244 | ### Android template 245 | # Built application files 246 | 247 | # Files for the ART/Dalvik VM 248 | 249 | # Java class files 250 | 251 | # Generated files 252 | 253 | # Gradle files 254 | 255 | # Local configuration file (sdk path, etc) 256 | 257 | # Proguard folder generated by Eclipse 258 | 259 | # Log Files 260 | 261 | # Android Studio Navigation editor temp files 262 | 263 | # Android Studio captures folder 264 | 265 | # IntelliJ 266 | # Android Studio 3 in .gitignore file. 267 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 268 | 269 | # Keystore files 270 | # Uncomment the following lines if you do not want to check your keystore files in. 271 | #*.jks 272 | #*.keystore 273 | 274 | # External native build folder generated in Android Studio 2.2 and later 275 | 276 | # Google Services (e.g. APIs or Firebase) 277 | # google-services.json 278 | 279 | # Freeline 280 | 281 | # fastlane 282 | 283 | # Version control 284 | 285 | # lint 286 | # lint/reports/ 287 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Language](https://img.shields.io/badge/language-kotlin-blue?logo=kotlin) ![License](https://img.shields.io/badge/License-MIT-orange) [![](https://jitpack.io/v/enginebai/AndroidBase.svg)](https://jitpack.io/#enginebai/AndroidBase) 2 | 3 | # AndroidBase 4 | The `AndroidBase` project provides an Android app project template that setups for **Gradle Kotlin DSL**, it also provides some base classes or extensions to accerate your android development. 5 | 6 | You can [use this template](https://github.com/enginebai/AndroidBase/generate) or download the [base module](#Download-for-Base-Module). 7 | 8 | There also provides two helper branches: 9 | * If you'd like to see how the simple Gradle Kotlin DSL project works (i.e. a new android app project just created by Android Studio and use Gradle Kotlin DSL), you can checkout to the branch [`gradle-kotlin-dsl`](https://github.com/enginebai/AndroidBase/tree/gradle-kotlin-dsl) to take a look. 10 | * If you'd like to see how the base module works or contribute to this project, you can checkout to the branch [`library`](https://github.com/enginebai/AndroidBase/tree/library). 11 | 12 | ## Setup for Template Project 13 | 1. Just click on [![Clone this template](https://img.shields.io/badge/-Clone%20template-brightgreen)](https://github.com/enginebai/AndroidBase/generate) button to create a new repo starting from this template. Or you can clone this project by `git clone git@github.com:enginebai/AndroidBase.git` . 14 | 1. Change your project name in `settings.gradle.kts`. 15 | 1. Set your application ID in `Versions.kt` 16 | 1. Set the package name in `AndroidManifest.xml` file of `:app` module . 17 | 1. Select `com.enginebai.project` directory in "Project" tool window and rename package for your app. 18 | 1. Specify your retrofit base URL in `NetworkModule.kt` file. 19 | 1. Start to design your main layout xml file `fragment_main.xml` and fragment class. 20 | 1. Specify your `MainFragment.kt` name in navigation graph xml file. 21 | 1. That's all. Start your app development journey now 🎉. 22 | 23 | ## Get Started with Template Project 24 | 1. You can start your development as usual in `app` module. 25 | 1. This projects encourges you to use single activity architecture with [naivgation component](https://developer.android.com/guide/navigation), you will create new fragment that **extends the `BaseFragment`** for your all UI pages. 26 | 1. This project uses [koin](https://github.com/InsertKoinIO/koin) as our dependency injection framework, you will define the modules in `di` package and add those modules in `AppContext.defineDependencies()` 27 | 1. You will handle errors with `ExceptionHandler`, we register a function that will handle errors that are passed to `Subscriber.onError(Throwable)` for RxJava; for non-RxJava exception, you will inject `ExceptionHandler` and pass exception to `accept(Throwable)` function. More detail usage you can check `ExceptionHandler`, there are some instructions that guide you how to write your custom exception handling logic. 28 | 29 | ## Good Practices of Gradle Kotlin DSL 30 | * Add all dependencies versions in `Versions.kt` 31 | 32 | ```kotlin 33 | object Versions { 34 | const val kotlin = "1.3.50" 35 | const val awesomeLibrary = "x.y.z" 36 | // TODO: add the library version 37 | ... 38 | } 39 | ``` 40 | * Define all 3rd-party dependencies in `Dependencies.kt`, and use all versions definition in `Versions.kt`. 41 | 42 | ```kotlin 43 | object Dependencies { 44 | const val rxJava = "io.reactivex.rxjava2:rxjava:${Versions.rxJava}" 45 | // TODO: add standalone dependency here! 46 | ... 47 | 48 | object Kotlin { 49 | const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" 50 | const val stdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}" 51 | } 52 | 53 | // TODO: add inner object for sub-modules of library 54 | object AndroidX { 55 | ... 56 | } 57 | ... 58 | } 59 | ``` 60 | 61 | * Always import dependency from `Dependencies.kt` in `build.gradle.kts` file. 62 | 63 | ```kotlin 64 | dependencies { 65 | implementation(Dependencies.Glide.core) 66 | "kapt"(Dependencies.Glide.compiler) 67 | implementation(project(":base")) 68 | // TODO: add by using dependency imported from `Dependencies.kt` file 69 | ... 70 | } 71 | ``` 72 | 73 | * Configure android build script in `Config.kt`. 74 | 75 | ```kotlin 76 | fun Project.configAndroid() = this.extensions.getByType().run { 77 | compileSdkVersion(Versions.Android.sdk) 78 | defaultConfig { 79 | minSdkVersion(Versions.Android.minSdk) 80 | targetSdkVersion(Versions.Android.sdk) 81 | versionCode = Versions.App.versionCode 82 | versionName = Versions.App.versionName 83 | // TODO: add your configurations 84 | ... 85 | } 86 | ... 87 | } 88 | ``` 89 | 90 | It's equivalent to the old way `android { ... }` block in `build.gradle` file 91 | ```groovy 92 | android { 93 | compileSdkVersion 21 94 | buildToolsVersion "21.1.2" 95 | defaultConfig { 96 | applicationId "com.enginebai.moviehunt" 97 | // TODO: add your configurations 98 | ... 99 | } 100 | ... 101 | } 102 | ``` 103 | 104 | 105 | * Add all configuration variables inside `Config` object in `Config.kt`, and add `buildConfigField(...)` to include. 106 | 107 | ```kotlin 108 | object Config { 109 | const val API_ROOT = "\"https://api.themoviedb.org/3/\"" 110 | const val IMAGE_API_ROOT = "\"https://image.tmdb.org/t/p/\"" 111 | // TODO: add your constants here, make sure to add extra double quotes for string value. 112 | } 113 | 114 | fun Project.configAndroid() = this.extensions.getByType().run { 115 | compileSdkVersion(Versions.Android.sdk) 116 | defaultConfig { 117 | ... 118 | 119 | buildConfigField("String", "API_ROOT", Config.API_ROOT) 120 | buildConfigField("String", "IMAGE_API_KEY", Config.IMAGE_API_ROOT) 121 | // TODO: add your varialbes here imported from `Config` object 122 | ... 123 | } 124 | ... 125 | } 126 | ``` 127 | 128 | * Add the common dependencies that share between modules to `Dependencies.kt` 129 | 130 | ```kotlin 131 | fun Project.importCommonDependencies() { 132 | dependencies { 133 | ... 134 | implementation(Dependencies.material) 135 | // TODO: add your common dependencies 136 | .. 137 | } 138 | } 139 | ``` 140 | 141 | > **Note:** Remember to perform Gradle Sync to apply your changes when updating any files in `buildSrc`. 142 | 143 | ## Modules Structure 144 | * `:app` module: That's your app module, just like a normal Android app project. Or you can create a new modules (ex: `:common`) for that if you use multi-modules project or libraries. There is the sample `build.gradle.kts` for library module: 145 | 146 | ```kotlin 147 | plugins { 148 | id("com.android.library") 149 | commonPlugins.forEach { id(it) } 150 | } 151 | 152 | configAndroid() 153 | importCommonDependencies() 154 | 155 | dependencies { 156 | implementation(Dependencies.whatever) 157 | ... 158 | } 159 | ``` 160 | * `/buildSrc`: It enables you to write the build script (`*.gradle.kts` files) in kotlin to manage dependencies and gets better IDE completion support. It gives you a way to develop build code more like regular code. More information please check [official document](https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:build_sources). 161 | 162 | ## Download for Base Module 163 | Step 1. Add it to your root `build.gradle.kts`: 164 | 165 | ```kotlin 166 | allprojects { 167 | repositories { 168 | ... 169 | maven("https://jitpack.io") 170 | } 171 | } 172 | ``` 173 | Step 2. Add the dependency: 174 | 175 | * `Versions.kt`: 176 | ```kotlin 177 | const val androidBase = "1.0.0" 178 | ``` 179 | 180 | * `Dependencies.kt`: 181 | ```kotlin 182 | const val androidBase = "com.github.enginebai:AndroidBase:${Versions.androidBase}" 183 | ``` 184 | 185 | * App module `build.gradle.kts`: 186 | ```kotlin 187 | dependencies { 188 | ... 189 | implementation(Dependencies.androidBase) 190 | } 191 | ``` 192 | 193 | ## Included Libraries 194 | There are some default 3rd-party libraries imported in this project, and provide some popular dependencies (following listed) in `buildSrc/Dependencies.kt` file that you can choose to use. Feel free to add/remove those dependencies. 195 | 196 | * [Android Architecture Components](https://developer.android.com/topic/libraries/architecture), part of Android Jetpack for give to project a robust design, testable and maintainable. 197 | * [Retrofit](https://github.com/square/retrofit) / [OkHttp](https://github.com/square/okhttp), Square open-source RESTful API and http client. 198 | * [RxJava](https://github.com/ReactiveX/RxJava/) / [RxAndroid](https://github.com/ReactiveX/RxAndroid), reactive programming for JVM. 199 | * [Koin](https://github.com/InsertKoinIO/koin), kotlin light-weight dependency injection. 200 | * [Timber](https://github.com/JakeWharton/timber), for logging. 201 | * [Epoxy](https://github.com/airbnb/epoxy), for RecyclerView complex view layout. 202 | * [Paging](https://developer.android.com/topic/libraries/architecture/paging), for pagination loading of RecyclerView. 203 | * [Navigation](https://developer.android.com/guide/navigation), for single activity and fragment routing. 204 | * [Room](https://developer.android.com/training/data-storage/room), for local persistence database. 205 | 206 | ## Useful Extensions 207 | * See [Extension Functions.](./EXTENSIONS.md) 208 | 209 | ## LICENSE 210 | 211 | ``` 212 | Copyright (c) 2020 Engine Bai 213 | 214 | Permission is hereby granted, free of charge, to any person obtaining a copy 215 | of this software and associated documentation files (the "Software"), to deal 216 | in the Software without restriction, including without limitation the rights 217 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 218 | copies of the Software, and to permit persons to whom the Software is 219 | furnished to do so, subject to the following conditions: 220 | 221 | The above copyright notice and this permission notice shall be included in all 222 | copies or substantial portions of the Software. 223 | 224 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 225 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 226 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 227 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 228 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 229 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 230 | SOFTWARE. 231 | ``` 232 | 233 | 234 | --------------------------------------------------------------------------------