Skip to content

Commit

Permalink
Merge pull request #103 from nimblehq/feature/101-backend-check-inter…
Browse files Browse the repository at this point in the history
…net-connection

[#101] [Backend] As a user, I can see dialog when there is no internet connection
  • Loading branch information
kaungkhantsoe authored Aug 4, 2023
2 parents 6e752c4 + 5af8ad3 commit 5f5c3d2
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 9 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package="co.nimblehq.compose.crypto">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>

<application
android:name=".CryptoComposeApplication"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package co.nimblehq.compose.crypto.di.modules

import android.content.Context
import co.nimblehq.compose.crypto.data.repository.GlobalRepositoryImpl
import co.nimblehq.compose.crypto.domain.repository.GlobalRepository
import co.nimblehq.compose.crypto.util.DispatchersProvider
import co.nimblehq.compose.crypto.util.DispatchersProviderImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent

@Module
Expand All @@ -14,4 +18,12 @@ class AppModule {
fun provideDispatchersProvider(): DispatchersProvider {
return DispatchersProviderImpl()
}

@Provides
fun provideGlobalRepository(
@ApplicationContext
appContext: Context
): GlobalRepository {
return GlobalRepositoryImpl(appContext)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ fun HomeScreen(
}
}

// TODO remove in integration ticket
val isNetworkConnected by viewModel.hasConnection.collectAsState()
LaunchedEffect(isNetworkConnected) {
if (isNetworkConnected != null) {
Toast.makeText(context,"Connection: $isNetworkConnected", Toast.LENGTH_SHORT).show()
}
}

val showMyCoinsLoading: IsLoading by viewModel.output.showMyCoinsLoading.collectAsState()
val showTrendingCoinsLoading: LoadingState by viewModel.output.showTrendingCoinsLoading.collectAsState()
val myCoins: List<CoinItemUiModel> by viewModel.output.myCoins.collectAsState()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package co.nimblehq.compose.crypto.ui.screens.home

import co.nimblehq.compose.crypto.domain.usecase.GetMyCoinsUseCase
import co.nimblehq.compose.crypto.domain.usecase.GetTrendingCoinsUseCase
import co.nimblehq.compose.crypto.domain.usecase.*
import co.nimblehq.compose.crypto.lib.IsLoading
import co.nimblehq.compose.crypto.ui.base.*
import co.nimblehq.compose.crypto.ui.navigation.AppDestination
Expand Down Expand Up @@ -52,7 +51,8 @@ interface Output : BaseOutput {
class HomeViewModel @Inject constructor(
dispatchers: DispatchersProvider,
private val getMyCoinsUseCase: GetMyCoinsUseCase,
private val getTrendingCoinsUseCase: GetTrendingCoinsUseCase
private val getTrendingCoinsUseCase: GetTrendingCoinsUseCase,
private val isNetworkConnectedUseCase: IsNetworkConnectedUseCase,
) : BaseViewModel(dispatchers), Input, Output {

override val input = this
Expand Down Expand Up @@ -84,8 +84,19 @@ class HomeViewModel @Inject constructor(

private var trendingCoinsPage = MY_COINS_INITIAL_PAGE

// TODO remove in integration ticket
private val _hasConnection = MutableStateFlow<Boolean?>(null)
val hasConnection: StateFlow<Boolean?> = _hasConnection

init {
loadData()
// TODO remove in integration ticket
execute {
isNetworkConnectedUseCase()
.collect {
_hasConnection.emit(it)
}
}
}

override fun loadData(isRefreshing: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.*
import androidx.navigation.*
import co.nimblehq.compose.crypto.R
import co.nimblehq.compose.crypto.domain.usecase.GetMyCoinsUseCase
import co.nimblehq.compose.crypto.domain.usecase.GetTrendingCoinsUseCase
import co.nimblehq.compose.crypto.domain.usecase.*
import co.nimblehq.compose.crypto.extension.toFormattedString
import co.nimblehq.compose.crypto.test.MockUtil
import co.nimblehq.compose.crypto.ui.navigation.AppDestination
Expand Down Expand Up @@ -55,12 +54,16 @@ class HomeScreenTest : BaseViewModelTest() {
private val mockGetMyCoinsUseCase = mockk<GetMyCoinsUseCase>()
private val mockGetTrendingCoinsUseCase = mockk<GetTrendingCoinsUseCase>()

// TODO remove in integration ticket
private val mockIsNetworkConnectedUseCase = mockk<IsNetworkConnectedUseCase>()

private lateinit var viewModel: HomeViewModel

private var appDestination: AppDestination? = null

@Before
fun setUp() {
every { mockIsNetworkConnectedUseCase() } returns flowOf(null)
composeAndroidTestRule.activity.setContent {
HomeScreen(
viewModel = viewModel,
Expand Down Expand Up @@ -216,7 +219,8 @@ class HomeScreenTest : BaseViewModelTest() {
viewModel = HomeViewModel(
dispatchers = testDispatcherProvider,
getMyCoinsUseCase = mockGetMyCoinsUseCase,
getTrendingCoinsUseCase = mockGetTrendingCoinsUseCase
getTrendingCoinsUseCase = mockGetTrendingCoinsUseCase,
isNetworkConnectedUseCase = mockIsNetworkConnectedUseCase
)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package co.nimblehq.compose.crypto.ui.screens.home

import app.cash.turbine.test
import co.nimblehq.compose.crypto.domain.usecase.GetMyCoinsUseCase
import co.nimblehq.compose.crypto.domain.usecase.GetTrendingCoinsUseCase
import co.nimblehq.compose.crypto.domain.usecase.*
import co.nimblehq.compose.crypto.test.MockUtil
import co.nimblehq.compose.crypto.ui.navigation.AppDestination
import co.nimblehq.compose.crypto.ui.screens.BaseViewModelTest
Expand All @@ -25,6 +24,9 @@ class HomeViewModelTest : BaseViewModelTest() {
private val mockGetTrendingCoinsUseCase = mockk<GetTrendingCoinsUseCase>()
private lateinit var viewModel: HomeViewModel

// TODO remove in integration ticket
private val mockIsNetworkConnectedUseCase = mockk<IsNetworkConnectedUseCase>()

@Before
fun setUp() {
every { mockGetMyCoinsUseCase.execute(any()) } returns flowOf(MockUtil.myCoins)
Expand Down Expand Up @@ -143,7 +145,8 @@ class HomeViewModelTest : BaseViewModelTest() {
viewModel = HomeViewModel(
testDispatcherProvider,
mockGetMyCoinsUseCase,
mockGetTrendingCoinsUseCase
mockGetTrendingCoinsUseCase,
mockIsNetworkConnectedUseCase
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package co.nimblehq.compose.crypto.data.repository

import android.content.Context
import android.net.*
import co.nimblehq.compose.crypto.domain.repository.GlobalRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

class GlobalRepositoryImpl(
context: Context
) : GlobalRepository {

private val isNetworkConnected: MutableStateFlow<Boolean?> = MutableStateFlow(null)

private val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build()

private val networkCallback = object : ConnectivityManager.NetworkCallback() {
// network is available for use
override fun onAvailable(network: Network) {
super.onAvailable(network)
isNetworkConnected.value = true
}

// Network capabilities have changed for the network
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
val unmetered = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
if (unmetered) {
isNetworkConnected.value = true
}
}

// lost network connection
override fun onLost(network: Network) {
super.onLost(network)
isNetworkConnected.value = false
}
}

init {
val connectivityManager = context.getSystemService(ConnectivityManager::class.java) as ConnectivityManager
connectivityManager.requestNetwork(networkRequest, networkCallback)
}

override fun isNetworkConnected(): Flow<Boolean?> = isNetworkConnected
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.nimblehq.compose.crypto.domain.repository

import kotlinx.coroutines.flow.Flow

interface GlobalRepository {
fun isNetworkConnected(): Flow<Boolean?>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package co.nimblehq.compose.crypto.domain.usecase

import co.nimblehq.compose.crypto.domain.repository.GlobalRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class IsNetworkConnectedUseCase @Inject constructor(private val repository: GlobalRepository) {
operator fun invoke(): Flow<Boolean?> = repository.isNetworkConnected()
}

0 comments on commit 5f5c3d2

Please sign in to comment.