Skip to content

Commit 9371d0d

Browse files
authored
Merge pull request #304 from android/tj/sync-ui
Offline-first UX improvements
2 parents f68a112 + c4debb7 commit 9371d0d

File tree

40 files changed

+686
-91
lines changed

40 files changed

+686
-91
lines changed

.github/workflows/Build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
androidTest:
6262
needs: build
6363
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
64-
timeout-minutes: 45
64+
timeout-minutes: 55
6565
strategy:
6666
matrix:
6767
api-level: [23, 26, 30]

app/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ dependencies {
8888
implementation(project(":core:designsystem"))
8989
implementation(project(":core:navigation"))
9090

91-
implementation(project(":sync"))
91+
implementation(project(":sync:work"))
92+
implementation(project(":sync:sync-test"))
9293

9394
androidTestImplementation(project(":core:testing"))
9495
androidTestImplementation(project(":core:datastore-test"))
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.nowinandroid.core.data.test
18+
19+
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
20+
import javax.inject.Inject
21+
import kotlinx.coroutines.flow.Flow
22+
import kotlinx.coroutines.flow.flowOf
23+
24+
class AlwaysOnlineNetworkMonitor @Inject constructor() : NetworkMonitor {
25+
override val isOnline: Flow<Boolean> = flowOf(true)
26+
}

core/data-test/src/main/java/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeAuthor
2525
import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeNewsRepository
2626
import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeTopicsRepository
2727
import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeUserDataRepository
28+
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
2829
import dagger.Binds
2930
import dagger.Module
3031
import dagger.hilt.components.SingletonComponent
@@ -55,4 +56,9 @@ interface TestDataModule {
5556
fun bindsUserDataRepository(
5657
userDataRepository: FakeUserDataRepository
5758
): UserDataRepository
59+
60+
@Binds
61+
fun bindsNetworkMonitor(
62+
networkMonitor: AlwaysOnlineNetworkMonitor
63+
): NetworkMonitor
5864
}

core/data/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ dependencies {
3030
testImplementation(project(":core:testing"))
3131
testImplementation(project(":core:datastore-test"))
3232

33+
implementation(libs.androidx.core.ktx)
34+
3335
implementation(libs.kotlinx.datetime)
3436
implementation(libs.kotlinx.coroutines.android)
3537
implementation(libs.kotlinx.serialization.json)

core/data/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@
1616
-->
1717
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
1818
package="com.google.samples.apps.nowinandroid.core.data">
19-
19+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2020
</manifest>

core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import com.google.samples.apps.nowinandroid.core.data.repository.OfflineFirstTop
2424
import com.google.samples.apps.nowinandroid.core.data.repository.OfflineFirstUserDataRepository
2525
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
2626
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
27+
import com.google.samples.apps.nowinandroid.core.data.util.ConnectivityManagerNetworkMonitor
28+
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
2729
import dagger.Binds
2830
import dagger.Module
2931
import dagger.hilt.InstallIn
@@ -52,4 +54,9 @@ interface DataModule {
5254
fun bindsUserDataRepository(
5355
userDataRepository: OfflineFirstUserDataRepository
5456
): UserDataRepository
57+
58+
@Binds
59+
fun bindsNetworkMonitor(
60+
networkMonitor: ConnectivityManagerNetworkMonitor
61+
): NetworkMonitor
5562
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.nowinandroid.core.data.util
18+
19+
import android.content.Context
20+
import android.net.ConnectivityManager
21+
import android.net.ConnectivityManager.NetworkCallback
22+
import android.net.Network
23+
import android.net.NetworkCapabilities
24+
import android.net.NetworkRequest.Builder
25+
import android.os.Build.VERSION
26+
import android.os.Build.VERSION_CODES
27+
import androidx.core.content.getSystemService
28+
import dagger.hilt.android.qualifiers.ApplicationContext
29+
import javax.inject.Inject
30+
import kotlinx.coroutines.channels.awaitClose
31+
import kotlinx.coroutines.flow.Flow
32+
import kotlinx.coroutines.flow.callbackFlow
33+
import kotlinx.coroutines.flow.conflate
34+
35+
class ConnectivityManagerNetworkMonitor @Inject constructor(
36+
@ApplicationContext private val context: Context
37+
) : NetworkMonitor {
38+
override val isOnline: Flow<Boolean> = callbackFlow<Boolean> {
39+
val callback = object : NetworkCallback() {
40+
override fun onAvailable(network: Network) {
41+
channel.trySend(true)
42+
}
43+
44+
override fun onLost(network: Network) {
45+
channel.trySend(false)
46+
}
47+
}
48+
49+
val connectivityManager = context.getSystemService<ConnectivityManager>()
50+
51+
connectivityManager?.registerNetworkCallback(
52+
Builder()
53+
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
54+
.build(),
55+
callback
56+
)
57+
58+
channel.trySend(connectivityManager.isCurrentlyConnected())
59+
60+
awaitClose {
61+
connectivityManager?.unregisterNetworkCallback(callback)
62+
}
63+
}
64+
.conflate()
65+
66+
@Suppress("DEPRECATION")
67+
private fun ConnectivityManager?.isCurrentlyConnected() = when (this) {
68+
null -> false
69+
else -> when {
70+
VERSION.SDK_INT >= VERSION_CODES.M ->
71+
activeNetwork
72+
?.let(::getNetworkCapabilities)
73+
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
74+
?: false
75+
else -> activeNetworkInfo?.isConnected ?: false
76+
}
77+
}
78+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.nowinandroid.core.data.util
18+
19+
import kotlinx.coroutines.flow.Flow
20+
21+
/**
22+
* Utility for reporting app connectivity status
23+
*/
24+
interface NetworkMonitor {
25+
val isOnline: Flow<Boolean>
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.nowinandroid.core.data.util
18+
19+
import kotlinx.coroutines.flow.Flow
20+
21+
/**
22+
* Reports on if synchronization is in progress
23+
*/
24+
interface SyncStatusMonitor {
25+
val isSyncing: Flow<Boolean>
26+
}

0 commit comments

Comments
 (0)