1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 13:57:10 +09:00

Fix/application start issues (#415)

This commit is contained in:
Evgenii Kozlov 2020-05-08 10:42:25 +02:00 committed by GitHub
parent 18fa6ba09c
commit f67533adb9
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 168 additions and 62 deletions

View file

@ -2,9 +2,13 @@ package com.agileburo.anytype.ui.splash
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.agileburo.anytype.BuildConfig
import com.agileburo.anytype.R
import com.agileburo.anytype.core_ui.extensions.visible
import com.agileburo.anytype.core_utils.ext.toast
import com.agileburo.anytype.core_utils.ui.ViewState
import com.agileburo.anytype.di.common.componentManager
import com.agileburo.anytype.presentation.splash.SplashViewModel
import com.agileburo.anytype.presentation.splash.SplashViewModelFactory
@ -17,7 +21,7 @@ import javax.inject.Inject
* email : ki@agileburo.com
* on 2019-10-21.
*/
class SplashFragment : NavigationFragment(R.layout.fragment_splash) {
class SplashFragment : NavigationFragment(R.layout.fragment_splash), Observer<ViewState<Nothing>> {
@Inject
lateinit var factory: SplashViewModelFactory
@ -30,7 +34,8 @@ class SplashFragment : NavigationFragment(R.layout.fragment_splash) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
vm.navigation.observe(this, navObserver)
vm.navigation.observe(viewLifecycleOwner, navObserver)
vm.state.observe(viewLifecycleOwner, this)
vm.onViewCreated()
showVersion()
}
@ -39,6 +44,13 @@ class SplashFragment : NavigationFragment(R.layout.fragment_splash) {
version.text = BuildConfig.VERSION_NAME
}
override fun onChanged(state: ViewState<Nothing>) {
if (state is ViewState.Error) {
toast(state.error)
error.visible()
}
}
override fun injectDependencies() {
componentManager().splashLoginComponent.get().inject(this)
}

View file

@ -1,14 +1,14 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/white">
<ImageView
android:id="@+id/logo"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_logo" />
<TextView
@ -21,4 +21,18 @@
android:textSize="12sp"
tools:text="0.0.10" />
<TextView
android:id="@+id/error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="100dp"
android:fontFamily="monospace"
android:text="@string/failed_to_launch_wallet"
android:textColor="#FF5722"
android:textSize="10sp"
android:visibility="invisible"
tools:ignore="SmallSp"
tools:visibility="visible" />
</FrameLayout>

View file

@ -108,5 +108,6 @@ Do the computation of an expensive paragraph of text on a background thread:
<string name="content_description_menu_icon">Menu icon</string>
<string name="add_new">Add new</string>
<string name="select_all">Select all</string>
<string name="failed_to_launch_wallet">Failed to launch wallet after retry.</string>
</resources>

View file

@ -4,6 +4,7 @@ import com.agileburo.anytype.domain.auth.repo.AuthRepository
import com.agileburo.anytype.domain.base.BaseUseCase
import com.agileburo.anytype.domain.base.Either
import com.agileburo.anytype.domain.device.PathProvider
import kotlinx.coroutines.withTimeout
/**
* Sets current wallet for current application session.
@ -14,13 +15,19 @@ class LaunchWallet(
) : BaseUseCase<Unit, BaseUseCase.None>() {
override suspend fun run(params: None) = try {
repository.recoverWallet(
mnemonic = repository.getMnemonic(),
path = pathProvider.providePath()
).let {
Either.Right(it)
withTimeout(TIMEOUT_DURATION) {
repository.recoverWallet(
mnemonic = repository.getMnemonic(),
path = pathProvider.providePath()
).let {
Either.Right(it)
}
}
} catch (e: Throwable) {
Either.Left(e)
}
companion object {
const val TIMEOUT_DURATION = 10000L
}
}

View file

@ -90,6 +90,8 @@ public class Middleware {
.setRootPath(path)
.build();
Timber.d("Recovering wallet...");
service.walletRecover(request);
}

View file

@ -4,12 +4,14 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.agileburo.anytype.core_utils.common.EventWrapper
import com.agileburo.anytype.core_utils.ui.ViewState
import com.agileburo.anytype.domain.auth.interactor.CheckAuthorizationStatus
import com.agileburo.anytype.domain.auth.interactor.LaunchAccount
import com.agileburo.anytype.domain.auth.interactor.LaunchWallet
import com.agileburo.anytype.domain.auth.model.AuthStatus
import com.agileburo.anytype.domain.base.BaseUseCase
import com.agileburo.anytype.presentation.navigation.AppNavigation
import kotlinx.coroutines.launch
import timber.log.Timber
/**
@ -23,11 +25,13 @@ class SplashViewModel(
private val launchAccount: LaunchAccount
) : ViewModel() {
val state = MutableLiveData<ViewState<Nothing>>()
val navigation: MutableLiveData<EventWrapper<AppNavigation.Command>> = MutableLiveData()
fun onViewCreated() {
checkAuthorizationStatus.invoke(viewModelScope, Unit) { result ->
result.either(
viewModelScope.launch {
checkAuthorizationStatus(Unit).either(
fnL = { e -> Timber.e(e, "Error while checking auth status") },
fnR = { status ->
if (status == AuthStatus.UNAUTHORIZED)
@ -40,17 +44,29 @@ class SplashViewModel(
}
private fun proceedWithLaunchingWallet() {
launchWallet.invoke(viewModelScope, BaseUseCase.None) { result ->
result.either(
fnL = { e -> Timber.e(e, "Error while launching wallet") },
viewModelScope.launch {
launchWallet(BaseUseCase.None).either(
fnL = { retryLaunchingWallet() },
fnR = { proceedWithLaunchingAccount() }
)
}
}
private fun retryLaunchingWallet() {
viewModelScope.launch {
launchWallet(BaseUseCase.None).either(
fnL = { e ->
Timber.e(e, "Error while retrying launching wallet")
state.postValue(ViewState.Error(error = e.toString()))
},
fnR = { proceedWithLaunchingAccount() }
)
}
}
private fun proceedWithLaunchingAccount() {
launchAccount.invoke(viewModelScope, BaseUseCase.None) { result ->
result.either(
viewModelScope.launch {
launchAccount(BaseUseCase.None).either(
fnR = { navigation.postValue(EventWrapper(AppNavigation.Command.StartDesktopFromSplash)) },
fnL = { e -> Timber.e(e, "Error while launching account") }
)

View file

@ -1,12 +1,15 @@
package com.agileburo.anytype.presentation.splash
import MockDataFactory
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.agileburo.anytype.core_utils.ui.ViewState
import com.agileburo.anytype.domain.auth.interactor.CheckAuthorizationStatus
import com.agileburo.anytype.domain.auth.interactor.LaunchAccount
import com.agileburo.anytype.domain.auth.interactor.LaunchWallet
import com.agileburo.anytype.domain.auth.model.AuthStatus
import com.agileburo.anytype.domain.base.Either
import com.agileburo.anytype.presentation.navigation.AppNavigation
import com.agileburo.anytype.presentation.util.CoroutinesTestRule
import com.jraska.livedata.test
import com.nhaarman.mockitokotlin2.*
import kotlinx.coroutines.runBlocking
@ -21,6 +24,9 @@ class SplashViewModelTest {
@get:Rule
val rule = InstantTaskExecutorRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
@Mock
lateinit var checkAuthorizationStatus: CheckAuthorizationStatus
@ -44,15 +50,37 @@ class SplashViewModelTest {
}
@Test
fun `should not execute use case when view model is created`() = runBlocking {
verify(checkAuthorizationStatus, times(0)).invoke(any(), any(), any())
verifyNoMoreInteractions(checkAuthorizationStatus)
fun `should not execute use case when view model is created`() {
val status = AuthStatus.AUTHORIZED
val response = Either.Right(status)
stubCheckAuthStatus(response)
stubLaunchWallet()
stubLaunchAccount()
runBlocking {
verify(checkAuthorizationStatus, times(0)).invoke(any(), any(), any())
verify(checkAuthorizationStatus, times(0)).invoke(any())
verifyNoMoreInteractions(checkAuthorizationStatus)
}
}
@Test
fun `should start executing use case when view is created`() = runBlocking {
fun `should start executing use case when view is created`() {
val status = AuthStatus.AUTHORIZED
val response = Either.Right(status)
stubCheckAuthStatus(response)
stubLaunchWallet()
stubLaunchAccount()
vm.onViewCreated()
verify(checkAuthorizationStatus, times(1)).invoke(any(), any(), any())
runBlocking {
verify(checkAuthorizationStatus, times(1)).invoke(any())
}
}
@Test
@ -62,15 +90,15 @@ class SplashViewModelTest {
val response = Either.Right(status)
checkAuthorizationStatus.stub {
on { invoke(any(), any(), any()) } doAnswer { answer ->
answer.getArgument<(Either<Throwable, AuthStatus>) -> Unit>(2)(response)
}
}
stubCheckAuthStatus(response)
stubLaunchWallet()
stubLaunchAccount()
vm.onViewCreated()
verify(launchWallet, times(1)).invoke(any(), any(), any())
runBlocking {
verify(launchWallet, times(1)).invoke(any())
}
}
@Test
@ -80,22 +108,16 @@ class SplashViewModelTest {
val response = Either.Right(status)
checkAuthorizationStatus.stub {
on { invoke(any(), any(), any()) } doAnswer { answer ->
answer.getArgument<(Either<Throwable, AuthStatus>) -> Unit>(2)(response)
}
}
launchWallet.stub {
on { invoke(any(), any(), any()) } doAnswer { answer ->
answer.getArgument<(Either<Throwable, Unit>) -> Unit>(2)(Either.Right(Unit))
}
}
stubCheckAuthStatus(response)
stubLaunchWallet()
stubLaunchAccount()
vm.onViewCreated()
verify(launchWallet, times(1)).invoke(any(), any(), any())
verify(launchAccount, times(1)).invoke(any(), any(), any())
runBlocking {
verify(launchWallet, times(1)).invoke(any())
verify(launchAccount, times(1)).invoke(any())
}
}
@Test
@ -105,23 +127,9 @@ class SplashViewModelTest {
val response = Either.Right(status)
checkAuthorizationStatus.stub {
on { invoke(any(), any(), any()) } doAnswer { answer ->
answer.getArgument<(Either<Throwable, AuthStatus>) -> Unit>(2)(response)
}
}
launchAccount.stub {
on { invoke(any(), any(), any()) } doAnswer { answer ->
answer.getArgument<(Either<Throwable, Unit>) -> Unit>(2)(Either.Right(Unit))
}
}
launchWallet.stub {
on { invoke(any(), any(), any()) } doAnswer { answer ->
answer.getArgument<(Either<Throwable, Unit>) -> Unit>(2)(Either.Right(Unit))
}
}
stubCheckAuthStatus(response)
stubLaunchAccount()
stubLaunchWallet()
vm.onViewCreated()
@ -137,11 +145,7 @@ class SplashViewModelTest {
val response = Either.Right(status)
checkAuthorizationStatus.stub {
on { invoke(any(), any(), any()) } doAnswer { answer ->
answer.getArgument<(Either<Throwable, AuthStatus>) -> Unit>(2)(response)
}
}
stubCheckAuthStatus(response)
vm.onViewCreated()
@ -149,4 +153,54 @@ class SplashViewModelTest {
value.peekContent() == AppNavigation.Command.OpenStartLoginScreen
}
}
@Test
fun `should retry launching wallet after failed launch and emit error`() {
// SETUP
val status = AuthStatus.AUTHORIZED
val response = Either.Right(status)
val exception = Exception(MockDataFactory.randomString())
stubCheckAuthStatus(response)
stubLaunchWallet(response = Either.Left(exception))
// TESTING
val state = vm.state.test()
state.assertNoValue()
vm.onViewCreated()
state.assertValue { value -> value is ViewState.Error }
runBlocking {
verify(launchWallet, times(2)).invoke(any())
}
}
private fun stubCheckAuthStatus(response: Either.Right<AuthStatus>) {
checkAuthorizationStatus.stub {
onBlocking { invoke(eq(Unit)) } doReturn response
}
}
private fun stubLaunchWallet(
response: Either<Throwable, Unit> = Either.Right(Unit)
) {
launchWallet.stub {
onBlocking { invoke(any()) } doReturn response
}
}
private fun stubLaunchAccount() {
launchAccount.stub {
onBlocking { invoke(any()) } doReturn Either.Right(Unit)
}
}
}