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:
parent
18fa6ba09c
commit
f67533adb9
7 changed files with 168 additions and 62 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -90,6 +90,8 @@ public class Middleware {
|
|||
.setRootPath(path)
|
||||
.build();
|
||||
|
||||
Timber.d("Recovering wallet...");
|
||||
|
||||
service.walletRecover(request);
|
||||
}
|
||||
|
||||
|
|
|
@ -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") }
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue