diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 674414f..41871c2 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -15,6 +15,7 @@
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 703e5d4..3378229 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/app/build.gradle b/app/build.gradle
index df2d3b4..88a2a22 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -83,6 +83,10 @@ android {
ktlint
}
+ kotlinOptions {
+ freeCompilerArgs = ["-Xallow-result-return-type"]
+ }
+
task ktlint(type: JavaExec, group: "verification") {
description = "Check Kotlin code style."
classpath = configurations.ktlint
@@ -117,21 +121,21 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.core:core-ktx:1.2.0'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation "androidx.preference:preference-ktx:1.1.0"
- implementation 'com.google.android.material:material:1.1.0'
+ implementation "androidx.preference:preference-ktx:1.1.1"
+ implementation 'com.google.android.material:material:1.2.1'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.28.2'
- androidTestImplementation 'androidx.test:runner:1.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
- androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test:runner:1.3.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
- androidTestImplementation 'androidx.test:rules:1.2.0'
+ androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.3.1'
//---- ANDROID ARCH ROOM ----
implementation 'android.arch.persistence.room:runtime:1.1.1'
@@ -139,15 +143,18 @@ dependencies {
//---- ANDROID ARCH LIFECYCLE ----
implementation 'android.arch.lifecycle:common-java8:1.1.1'
kapt "android.arch.lifecycle:compiler:1.1.1"
- implementation 'android.arch.lifecycle:runtime:1.1.1'
- implementation 'android.arch.lifecycle:extensions:1.1.1'
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
+
//---- GOOGLE JSON SERIALIZER/DESERIALIZER ----
implementation 'com.google.code.gson:gson:2.8.5'
//---- MixPanel ----
implementation 'com.mixpanel.android:mixpanel-android:5.6.1'
//---- Firebase ----
- implementation 'com.google.firebase:firebase-core:17.2.2'
- implementation 'com.google.firebase:firebase-analytics:17.2.1'
+ implementation 'com.google.firebase:firebase-core:18.0.0'
+ implementation 'com.google.firebase:firebase-analytics:18.0.0'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'org.jetbrains.kotlin:kotlin-reflect:1.3.61'
//---- Image ----
diff --git a/app/src/main/java/com/rootstrap/android/network/managers/IUserManager.kt b/app/src/main/java/com/rootstrap/android/network/managers/IUserManager.kt
new file mode 100644
index 0000000..d30f5a6
--- /dev/null
+++ b/app/src/main/java/com/rootstrap/android/network/managers/IUserManager.kt
@@ -0,0 +1,11 @@
+package com.rootstrap.android.network.managers
+
+import com.rootstrap.android.network.models.User
+import com.rootstrap.android.network.models.UserSerializer
+import com.rootstrap.android.util.extensions.Data
+
+interface IUserManager {
+ suspend fun signUp(user: User): Result>
+ suspend fun signIn(user: User): Result>
+ suspend fun signOut(): Result>
+}
diff --git a/app/src/main/java/com/rootstrap/android/network/managers/UserManager.kt b/app/src/main/java/com/rootstrap/android/network/managers/UserManager.kt
index f324299..bb9475d 100644
--- a/app/src/main/java/com/rootstrap/android/network/managers/UserManager.kt
+++ b/app/src/main/java/com/rootstrap/android/network/managers/UserManager.kt
@@ -1,75 +1,31 @@
package com.rootstrap.android.network.managers
import androidx.annotation.RestrictTo
-import com.rootstrap.android.bus
import com.rootstrap.android.network.models.User
import com.rootstrap.android.network.models.UserSerializer
import com.rootstrap.android.network.providers.ServiceProvider
import com.rootstrap.android.network.services.ApiService
import com.rootstrap.android.util.extensions.ActionCallback
-import retrofit2.Response
+import com.rootstrap.android.util.extensions.Data
/**
* Singleton Object
* */
-object UserManager {
+class UserManager : IUserManager {
private var service = ServiceProvider.create(ApiService::class.java)
- fun signUp(user: User) {
- val userSerializer = UserSerializer(user)
- val signUp = service.signUp(userSerializer)
- signUp.enqueue(UserCallback())
- }
+ override suspend fun signUp(user: User): Result> =
+ ActionCallback.call(service.signUp(UserSerializer(user)))
- fun signIn(user: User) {
- val userSerializer = UserSerializer(user)
- val signIn = service.signIn(userSerializer)
- signIn.enqueue(LogInCallback())
- }
+ override suspend fun signIn(user: User): Result> =
+ ActionCallback.call(service.signIn(UserSerializer(user)))
- fun signOut() {
- val signOut = service.signOut()
- signOut.enqueue(LogOutCallback())
- }
-
- private class UserCallback : ActionCallback() {
- override fun responseAction(response: Response) {
- super.responseAction(response)
- response.body()?.let {
- SessionManager.user = it.user
- }
- bus.post(UserCreatedSuccessfullyEvent())
- }
- }
-
- private class LogInCallback : ActionCallback() {
-
- override fun responseAction(response: Response) {
- super.responseAction(response)
- response.body()?.let {
- SessionManager.signIn(it.user)
- }
- bus.post(SignInSuccessfullyEvent())
- }
- }
-
- private class LogOutCallback : ActionCallback() {
-
- override fun responseAction(response: Response) {
- super.responseAction(response)
-
- SessionManager.signOut()
- bus.post(SignedOutSuccessfullyEvent())
- }
- }
+ override suspend fun signOut(): Result> =
+ ActionCallback.call(service.signOut())
@RestrictTo(RestrictTo.Scope.TESTS)
- open fun reloadService(url: String) {
+ fun reloadService(url: String) {
service = ServiceProvider.create(ApiService::class.java, url)
}
-
- class UserCreatedSuccessfullyEvent
- class SignInSuccessfullyEvent
- class SignedOutSuccessfullyEvent
}
diff --git a/app/src/main/java/com/rootstrap/android/network/models/User.kt b/app/src/main/java/com/rootstrap/android/network/models/User.kt
index 2f490d3..99185e8 100644
--- a/app/src/main/java/com/rootstrap/android/network/models/User.kt
+++ b/app/src/main/java/com/rootstrap/android/network/models/User.kt
@@ -12,4 +12,4 @@ data class User(
@Json(name = "username") val username: String = ""
)
-data class UserSerializer(val user: User)
+data class UserSerializer(@Json(name = "user") val user: User)
diff --git a/app/src/main/java/com/rootstrap/android/ui/activity/main/ProfileActivity.kt b/app/src/main/java/com/rootstrap/android/ui/activity/main/ProfileActivity.kt
index 0f8403f..7b24c22 100644
--- a/app/src/main/java/com/rootstrap/android/ui/activity/main/ProfileActivity.kt
+++ b/app/src/main/java/com/rootstrap/android/ui/activity/main/ProfileActivity.kt
@@ -42,7 +42,7 @@ class ProfileActivity : BaseActivity(), ProfileView {
override fun updateState() {
when (viewModel.state) {
ProfileState.signOutFailure -> showError(viewModel.error)
- ProfileState.signedOutSuccessfully -> goToFirstScreen()
+ ProfileState.signOutSuccessfully -> goToFirstScreen()
else -> {
}
}
diff --git a/app/src/main/java/com/rootstrap/android/ui/activity/main/ProfileActivityViewModel.kt b/app/src/main/java/com/rootstrap/android/ui/activity/main/ProfileActivityViewModel.kt
index d2a8995..8e38de0 100644
--- a/app/src/main/java/com/rootstrap/android/ui/activity/main/ProfileActivityViewModel.kt
+++ b/app/src/main/java/com/rootstrap/android/ui/activity/main/ProfileActivityViewModel.kt
@@ -2,53 +2,53 @@ package com.rootstrap.android.ui.activity.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.rootstrap.android.network.managers.IUserManager
import com.rootstrap.android.network.managers.UserManager
import com.rootstrap.android.ui.base.BaseViewModel
import com.rootstrap.android.util.NetworkState
import com.rootstrap.android.util.ViewModelListener
-import com.rootstrap.android.util.extensions.ErrorEvent
-import com.rootstrap.android.util.extensions.FailureEvent
-import com.squareup.otto.Subscribe
+import com.rootstrap.android.util.extensions.ApiErrorType
+import com.rootstrap.android.util.extensions.ApiException
+import kotlinx.coroutines.launch
open class ProfileActivityViewModel(listener: ViewModelListener?) : BaseViewModel(listener) {
- private val manager = UserManager
+ private val manager: IUserManager = UserManager()
fun signOut() {
networkState = NetworkState.loading
- manager.signOut()
- }
-
- var state: ProfileState = ProfileState.none
- set(value) {
- field = value
- listener?.updateState()
+ viewModelScope.launch {
+ val result = manager.signOut()
+ if (result.isSuccess) {
+ networkState = NetworkState.idle
+ state = ProfileState.signOutSuccessfully
+ } else {
+ manageError(result.exceptionOrNull())
+ }
}
-
- @Subscribe
- fun signedOutSuccessfully(event: UserManager.SignedOutSuccessfullyEvent) {
- networkState = NetworkState.idle
- state = ProfileState.signedOutSuccessfully
}
- @Subscribe
- fun signOutError(event: ErrorEvent) {
- error = event.error
- networkState = NetworkState.idle
- networkState = NetworkState.error
- }
+ private fun manageError(exception: Throwable?) {
+ error = if (exception is ApiException && exception.errorType == ApiErrorType.apiError) {
+ exception.message
+ } else null
- @Subscribe
- fun signOutFailure(event: FailureEvent) {
- error = null
networkState = NetworkState.idle
+ networkState = NetworkState.error
state = ProfileState.signOutFailure
}
+
+ var state: ProfileState = ProfileState.none
+ set(value) {
+ field = value
+ listener?.updateState()
+ }
}
enum class ProfileState {
signOutFailure,
- signedOutSuccessfully,
+ signOutSuccessfully,
none,
}
diff --git a/app/src/main/java/com/rootstrap/android/ui/activity/main/SignInActivity.kt b/app/src/main/java/com/rootstrap/android/ui/activity/main/SignInActivity.kt
index be29db0..2ea67eb 100644
--- a/app/src/main/java/com/rootstrap/android/ui/activity/main/SignInActivity.kt
+++ b/app/src/main/java/com/rootstrap/android/ui/activity/main/SignInActivity.kt
@@ -52,8 +52,8 @@ class SignInActivity : PermissionActivity(), AuthView {
private val viewModelListener = object : ViewModelListener {
override fun updateState() {
when (viewModel.state) {
- SignInState.signedInFailure -> showError(viewModel.error)
- SignInState.signedInSuccess -> showProfile()
+ SignInState.signInFailure -> showError(viewModel.error)
+ SignInState.signInSuccessfully -> showProfile()
else -> {
}
}
diff --git a/app/src/main/java/com/rootstrap/android/ui/activity/main/SignInActivityViewModel.kt b/app/src/main/java/com/rootstrap/android/ui/activity/main/SignInActivityViewModel.kt
index 3ea6f64..df138aa 100644
--- a/app/src/main/java/com/rootstrap/android/ui/activity/main/SignInActivityViewModel.kt
+++ b/app/src/main/java/com/rootstrap/android/ui/activity/main/SignInActivityViewModel.kt
@@ -2,18 +2,21 @@ package com.rootstrap.android.ui.activity.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.rootstrap.android.network.managers.IUserManager
+import com.rootstrap.android.network.managers.SessionManager
import com.rootstrap.android.network.managers.UserManager
import com.rootstrap.android.network.models.User
import com.rootstrap.android.ui.base.BaseViewModel
import com.rootstrap.android.util.NetworkState
import com.rootstrap.android.util.ViewModelListener
-import com.rootstrap.android.util.extensions.ErrorEvent
-import com.rootstrap.android.util.extensions.FailureEvent
-import com.squareup.otto.Subscribe
+import com.rootstrap.android.util.extensions.ApiErrorType
+import com.rootstrap.android.util.extensions.ApiException
+import kotlinx.coroutines.launch
open class SignInActivityViewModel(listener: ViewModelListener?) : BaseViewModel(listener) {
- private val manager = UserManager
+ private val manager: IUserManager = UserManager()
var state: SignInState = SignInState.none
set(value) {
@@ -23,33 +26,35 @@ open class SignInActivityViewModel(listener: ViewModelListener?) : BaseViewModel
fun signIn(user: User) {
networkState = NetworkState.loading
- manager.signIn(user)
- }
+ viewModelScope.launch {
+ val result = manager.signIn(user = user)
+ if (result.isSuccess) {
+ result.getOrNull()?.value?.user?.let { user ->
+ SessionManager.signIn(user)
+ }
- @Subscribe
- fun signedInSuccessfully(event: UserManager.SignInSuccessfullyEvent) {
- networkState = NetworkState.idle
- state = SignInState.signedInSuccess
+ networkState = NetworkState.idle
+ state = SignInState.signInSuccessfully
+ } else {
+ manageError(result.exceptionOrNull())
+ }
+ }
}
- @Subscribe
- fun signedInError(event: ErrorEvent) {
- error = event.error
- networkState = NetworkState.idle
- networkState = NetworkState.error
- }
+ private fun manageError(exception: Throwable?) {
+ error = if (exception is ApiException && exception.errorType == ApiErrorType.apiError) {
+ exception.message
+ } else null
- @Subscribe
- fun signedInFailure(event: FailureEvent) {
- error = null
networkState = NetworkState.idle
- state = SignInState.signedInFailure
+ networkState = NetworkState.error
+ state = SignInState.signInFailure
}
}
enum class SignInState {
- signedInFailure,
- signedInSuccess,
+ signInFailure,
+ signInSuccessfully,
none,
}
diff --git a/app/src/main/java/com/rootstrap/android/ui/activity/main/SignUpActivity.kt b/app/src/main/java/com/rootstrap/android/ui/activity/main/SignUpActivity.kt
index 627bec9..ad1dabc 100644
--- a/app/src/main/java/com/rootstrap/android/ui/activity/main/SignUpActivity.kt
+++ b/app/src/main/java/com/rootstrap/android/ui/activity/main/SignUpActivity.kt
@@ -52,8 +52,8 @@ class SignUpActivity : BaseActivity(), AuthView {
private val viewModelListener = object : ViewModelListener {
override fun updateState() {
when (viewModel.state) {
- SignUpState.signedUpFailure -> showError(viewModel.error)
- SignUpState.signedUpSuccess -> showProfile()
+ SignUpState.signUpFailure -> showError(viewModel.error)
+ SignUpState.signUpSuccessfully -> showProfile()
else -> {
}
}
diff --git a/app/src/main/java/com/rootstrap/android/ui/activity/main/SignUpActivityViewModel.kt b/app/src/main/java/com/rootstrap/android/ui/activity/main/SignUpActivityViewModel.kt
index 9545e63..ebe198e 100644
--- a/app/src/main/java/com/rootstrap/android/ui/activity/main/SignUpActivityViewModel.kt
+++ b/app/src/main/java/com/rootstrap/android/ui/activity/main/SignUpActivityViewModel.kt
@@ -2,18 +2,21 @@ package com.rootstrap.android.ui.activity.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.rootstrap.android.network.managers.IUserManager
+import com.rootstrap.android.network.managers.SessionManager
import com.rootstrap.android.network.managers.UserManager
import com.rootstrap.android.network.models.User
import com.rootstrap.android.ui.base.BaseViewModel
import com.rootstrap.android.util.NetworkState
import com.rootstrap.android.util.ViewModelListener
-import com.rootstrap.android.util.extensions.ErrorEvent
-import com.rootstrap.android.util.extensions.FailureEvent
-import com.squareup.otto.Subscribe
+import com.rootstrap.android.util.extensions.ApiErrorType
+import com.rootstrap.android.util.extensions.ApiException
+import kotlinx.coroutines.launch
open class SignUpActivityViewModel(listener: ViewModelListener?) : BaseViewModel(listener) {
- private val manager = UserManager
+ private val manager: IUserManager = UserManager()
var state: SignUpState = SignUpState.none
set(value) {
@@ -23,33 +26,36 @@ open class SignUpActivityViewModel(listener: ViewModelListener?) : BaseViewModel
fun signUp(user: User) {
networkState = NetworkState.loading
- manager.signUp(user)
- }
+ viewModelScope.launch {
+ val result = manager.signUp(user = user)
- @Subscribe
- fun signedUpSuccessfully(event: UserManager.UserCreatedSuccessfullyEvent) {
- networkState = NetworkState.idle
- state = SignUpState.signedUpSuccess
- }
+ if (result.isSuccess) {
+ result.getOrNull()?.value?.user?.let { user ->
+ SessionManager.signIn(user)
+ }
- @Subscribe
- fun signedUpError(event: ErrorEvent) {
- error = event.error
- networkState = NetworkState.idle
- networkState = NetworkState.error
+ networkState = NetworkState.idle
+ state = SignUpState.signUpSuccessfully
+ } else {
+ manageError(result.exceptionOrNull())
+ }
+ }
}
- @Subscribe
- fun signedUpFailure(event: FailureEvent) {
- error = null
+ private fun manageError(exception: Throwable?) {
+ error = if (exception is ApiException && exception.errorType == ApiErrorType.apiError) {
+ exception.message
+ } else null
+
networkState = NetworkState.idle
- state = SignUpState.signedUpFailure
+ networkState = NetworkState.error
+ state = SignUpState.signUpFailure
}
}
enum class SignUpState {
- signedUpFailure,
- signedUpSuccess,
+ signUpFailure,
+ signUpSuccessfully,
none,
}
diff --git a/app/src/main/java/com/rootstrap/android/util/extensions/ActionCallback.kt b/app/src/main/java/com/rootstrap/android/util/extensions/ActionCallback.kt
index 175ee6a..ef22a27 100644
--- a/app/src/main/java/com/rootstrap/android/util/extensions/ActionCallback.kt
+++ b/app/src/main/java/com/rootstrap/android/util/extensions/ActionCallback.kt
@@ -1,44 +1,57 @@
package com.rootstrap.android.util.extensions
import com.google.gson.Gson
-import com.rootstrap.android.bus
import com.rootstrap.android.network.models.ErrorModel
-import com.rootstrap.android.util.ErrorUtil
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import retrofit2.Call
-import retrofit2.Callback
import retrofit2.Response
-abstract class ActionCallback : Callback {
+class ActionCallback {
- override fun onResponse(call: Call, response: Response) {
- if (response.isSuccessful) {
- responseAction(response)
- } else {
- errorAction(response)
- }
- }
+ companion object {
- override fun onFailure(call: Call, t: Throwable) {
- failureAction(t)
- }
-
- open fun responseAction(response: Response) {}
+ suspend fun call(apiCall: Call): Result> =
+ withContext(Dispatchers.IO) {
+ val response = apiCall.execute()
+ handleResponse(response)
+ }
- open fun errorAction(response: Response) {
- try {
- response.errorBody()?.let {
- val error = Gson().fromJson(it.charStream(), ErrorModel::class.java)
- bus.post(ErrorEvent(ErrorUtil.handleCustomError(error)))
+ private fun handleResponse(response: Response): Result> {
+ if (response.isSuccessful) {
+ return Result.success(
+ Data(response.body())
+ )
+ } else {
+ try {
+ response.errorBody()?.let {
+ val apiError = Gson().fromJson(it.charStream(), ErrorModel::class.java)
+ return Result.failure(
+ ApiException(
+ errorMessage = apiError.error
+ )
+ )
+ }
+ } catch (ignore: Exception) {
+ }
}
- } catch (e: Exception) {
- bus.post(FailureEvent())
+
+ return Result.failure(ApiException(errorType = ApiErrorType.unknownError))
}
}
+}
- open fun failureAction(throwable: Throwable?) {
- bus.post(FailureEvent())
- }
+class Data(val value: T?)
+
+class ApiException(
+ private val errorMessage: String? = null,
+ val errorType: ApiErrorType = ApiErrorType.apiError
+) : java.lang.Exception() {
+ override val message: String?
+ get() = errorMessage
}
-class ErrorEvent(val error: String)
-class FailureEvent
+enum class ApiErrorType {
+ apiError,
+ unknownError
+}