Skip to content

Commit fb9636e

Browse files
committed
Finish 1.2.0
# Conflicts: # README.md # src/main/java/net/kuama/wifiMonitor/WifiLiveData.kt # src/main/java/net/kuama/wifiMonitor/WifiMonitor.kt # src/main/java/net/kuama/wifiMonitor/data/WifiStatus.kt
2 parents a49ee90 + 5283217 commit fb9636e

File tree

8 files changed

+266
-81
lines changed

8 files changed

+266
-81
lines changed

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
## Unreleased
1+
# Version 1.2.0
2+
- Expose flow of `WifiStatus`
3+
- Kotlin 1.5
4+
- Added tests
5+
- Added WifiMonitor Builder
6+
- Added Permission Checker class
7+
- Added an alternative way to build a WifiMonitor object
8+
9+
# Version 1.1.1
10+
> 2021-06-10
11+
- Expose current `WiFiStatus`
212

313
# Version 1.1.1
414
> 2021-06-10

README.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ Allows you to watch for wi-fi changes on an Android device.
55

66
```kotlin
77
class WiFiViewModel(context: Context) : ViewModel() {
8-
val values = WifiLiveData(WifiMonitor(context))
8+
val values = WifiLiveData(
9+
WifiMonitor.WifiMonitorBuilder()
10+
.context(context)
11+
.build()
12+
)
913
}
1014

1115
class MainActivity : AppCompatActivity() {
@@ -22,17 +26,21 @@ class MainActivity : AppCompatActivity() {
2226
}
2327

2428
// Or more simply
25-
val monitor = WifiMonitor(context)
29+
val monitorFlow = WifiMonitor.WifiMonitorBuilder()
30+
.context(context)
31+
.build()
32+
.start()
2633

27-
// currently available information
28-
monitor.info
29-
30-
// observe changes
31-
lifecycleScope.launchWhenStarted {
32-
monitor.observe { freshInfo ->
33-
34-
}
35-
}
34+
// Or without passing the context
35+
val monitorFlow = WifiMonitor.WifiMonitorBuilder()
36+
.listener(wifiListener)
37+
.wifiManager(wifiManager)
38+
.permissionChecker(permissionChecker)
39+
.build()
40+
.start()
41+
42+
// last known information
43+
monitorFlow.first()
3644
}
3745
}
3846

build.gradle

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11

22
buildscript {
3-
ext.kotlin_version = "1.5.10"
3+
ext.kotlin_version = "1.5.30"
44
repositories {
55
google()
66
mavenCentral()
77
dependencies {
8-
classpath "com.android.tools.build:gradle:4.2.1"
8+
classpath "com.android.tools.build:gradle:4.2.2"
99
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1010
classpath "com.diffplug.spotless:spotless-plugin-gradle:5.12.5"
1111
}
@@ -57,19 +57,24 @@ android {
5757
}
5858
}
5959

60+
testOptions {
61+
unitTests.returnDefaultValues = true
62+
}
63+
6064
}
6165

6266
dependencies {
6367
implementation fileTree(dir: "libs", include: ["*.jar"])
64-
implementation "androidx.core:core-ktx:1.5.0"
68+
implementation "androidx.core:core-ktx:1.6.0"
6569
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
66-
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
67-
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
70+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
71+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
6872

73+
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1'
6974
testImplementation "junit:junit:4.13.2"
70-
testImplementation "io.mockk:mockk:1.11.0"
71-
androidTestImplementation "androidx.test.ext:junit:1.1.2"
72-
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
75+
testImplementation "io.mockk:mockk:1.12.0"
76+
androidTestImplementation "androidx.test.ext:junit:1.1.3"
77+
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
7378
}
7479

7580

src/main/java/net/kuama/wifiMonitor/WifiLiveData.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package net.kuama.wifiMonitor
22

33
import androidx.lifecycle.LiveData
44
import kotlinx.coroutines.*
5+
import kotlinx.coroutines.flow.collect
56
import net.kuama.wifiMonitor.data.WifiStatus
67

8+
@ExperimentalCoroutinesApi
79
class WifiLiveData constructor(private val monitor: WifiMonitor) :
810
LiveData<WifiStatus>() {
911

@@ -12,7 +14,9 @@ class WifiLiveData constructor(private val monitor: WifiMonitor) :
1214
override fun onActive() {
1315
super.onActive()
1416
startJob = CoroutineScope(SupervisorJob() + Dispatchers.Default).launch {
15-
monitor.observe(::postValue)
17+
monitor.start().collect { wifiStatus ->
18+
postValue(wifiStatus)
19+
}
1620
}
1721
}
1822

src/main/java/net/kuama/wifiMonitor/WifiMonitor.kt

Lines changed: 145 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,139 @@ import android.net.wifi.WifiManager
77
import android.os.Build
88
import androidx.core.content.ContextCompat
99
import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
10-
import kotlinx.coroutines.Dispatchers
11-
import kotlinx.coroutines.withContext
10+
import kotlinx.coroutines.ExperimentalCoroutinesApi
11+
import kotlinx.coroutines.channels.awaitClose
12+
import kotlinx.coroutines.flow.Flow
13+
import kotlinx.coroutines.flow.callbackFlow
1214
import net.kuama.wifiMonitor.data.WifiStatus
15+
import net.kuama.wifiMonitor.data.WifiStatus.State
1316
import net.kuama.wifiMonitor.implementation.AndroidQWifiListener
1417
import net.kuama.wifiMonitor.implementation.BeforeAndroidQWifiListener
1518

16-
class WifiMonitor(context: Context) {
19+
/**
20+
* This class allows to check if any permission from the manifest has been granted
21+
*/
22+
class PermissionChecker(private val context: Context) {
23+
class Builder {
24+
private var context: Context? = null
25+
fun context(context: Context) =
26+
apply {
27+
this.context = context
28+
}
1729

18-
private val listener = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
19-
AndroidQWifiListener(context)
20-
} else {
21-
// on android < 10 we need to take a totally different approach
22-
BeforeAndroidQWifiListener(context)
30+
fun build(): PermissionChecker {
31+
val context = checkNotNull(context) { "Please provide a valid context" }
32+
return PermissionChecker(context)
33+
}
2334
}
2435

25-
private val wifiManager: WifiManager =
26-
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
27-
2836
/**
29-
* Whether we received at least a Wi-Fi change status. When false, we cannot say we are connected
37+
* Passing a permission, it checks if the user granted.
38+
* The permission must be in Manifest.permission.PERMISSION_NAME form
3039
*/
31-
private var didReceiveChange = false
40+
fun check(permission: String): Boolean {
41+
return ContextCompat.checkSelfPermission(
42+
context,
43+
permission
44+
) == PERMISSION_GRANTED
45+
}
46+
}
3247

33-
/**
34-
* Stop listening to Wi-Fi changes
35-
*/
36-
fun stop() = listener.stop()
48+
class WifiMonitor private constructor(
49+
private val listener: WifiListener,
50+
private val wifiManager: WifiManager,
51+
permissionChecker: PermissionChecker
52+
) {
3753

3854
/**
39-
* Triggers the on change callback whenever the Wi-Fi changes its status
55+
* In order to be able to build a WifiMonitor instance, it is necessary to pass either:
56+
* - a valid context
57+
* or
58+
* - a WifiListener, WifiManager and a PermissionChecker
4059
*/
41-
suspend fun observe(onChange: (WifiStatus) -> Unit) =
42-
withContext(Dispatchers.Default) {
43-
listener.start {
44-
didReceiveChange = true
45-
onChange(info)
60+
class WifiMonitorBuilder {
61+
62+
private var listener: WifiListener? = null
63+
fun listener(listener: WifiListener) =
64+
apply {
65+
this.listener = listener
66+
}
67+
68+
private fun listenerBuilder(context: Context): WifiListener {
69+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
70+
AndroidQWifiListener(context)
71+
} else {
72+
// on android < 10 we need to take a totally different approach
73+
BeforeAndroidQWifiListener(context)
74+
}
75+
}
76+
77+
private var wifiManager: WifiManager? = null
78+
fun wifiManager(wifiManager: WifiManager) = apply {
79+
this.wifiManager = wifiManager
80+
}
81+
82+
private fun wifiManagerBuilder(context: Context): WifiManager {
83+
return context.applicationContext
84+
.getSystemService(Context.WIFI_SERVICE) as WifiManager
85+
}
86+
87+
private var permissionChecker: PermissionChecker? = null
88+
fun permissionChecker(permissionChecker: PermissionChecker) = apply {
89+
this.permissionChecker = permissionChecker
90+
}
91+
92+
private fun permissionCheckerBuilder(context: Context): PermissionChecker {
93+
return PermissionChecker.Builder()
94+
.context(context)
95+
.build()
96+
}
97+
98+
private var context: Context? = null
99+
fun context(context: Context) =
100+
apply {
101+
this.context = context
102+
}
103+
104+
fun build(): WifiMonitor {
105+
if (context == null) {
106+
// If we don't have a context, make sure we have all params to build a valid wifi monitor
107+
val listener = checkNotNull(listener) { "Please provide a valid wi-fi listener" }
108+
val wifiManager =
109+
checkNotNull(wifiManager) { "Please provide a valid wi-fi manager" }
110+
val permissionChecker =
111+
checkNotNull(permissionChecker) { "Please provide a valid permission checker" }
112+
return WifiMonitor(
113+
listener,
114+
wifiManager,
115+
permissionChecker
116+
)
117+
} else {
118+
// If we have a context, build all the missing params in order to have a wifi monitor
119+
val context = checkNotNull(context) { "Please provide a valid context" }
120+
val listener = if (listener == null) {
121+
listenerBuilder(context)
122+
} else {
123+
this.listener!!
124+
}
125+
val wifiManager = if (wifiManager == null) {
126+
wifiManagerBuilder(context)
127+
} else {
128+
this.wifiManager!!
129+
}
130+
val permissionChecker = if (permissionChecker == null) {
131+
permissionCheckerBuilder(context)
132+
} else {
133+
this.permissionChecker!!
134+
}
135+
return WifiMonitor(
136+
listener,
137+
wifiManager,
138+
permissionChecker
139+
)
46140
}
47141
}
142+
}
48143

49144
private val state: Int
50145
get() = wifiManager.wifiState
@@ -53,44 +148,52 @@ class WifiMonitor(context: Context) {
53148
get() = wifiManager.connectionInfo
54149

55150
private val band: WifiStatus.NetworkBand
56-
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
151+
get() =
57152
if (connectionInfo.frequency > 3000) {
58153
WifiStatus.NetworkBand.WIFI_5_GHZ
59154
} else {
60155
WifiStatus.NetworkBand.WIFI_2_4_GHZ
61156
}
62-
} else {
63-
WifiStatus.NetworkBand.UNKNOWN
64-
}
65157

66158
/**
67-
* Holds the current information on the Wi-Fi connection.
159+
* True if the user granted access to [Manifest.permission.ACCESS_FINE_LOCATION]
160+
*/
161+
private val isFineLocationAccessGranted: Boolean =
162+
permissionChecker.check(Manifest.permission.ACCESS_FINE_LOCATION)
163+
164+
/**
165+
* Start listening to Wi-fi changes exposing a flow of WifiStatus.
166+
* It can throw an exception when the channel publishing on a channel that is closed
68167
*/
69-
val info: WifiStatus
70-
get() = if (didReceiveChange) {
71-
when (state) {
168+
@ExperimentalCoroutinesApi
169+
suspend fun start(): Flow<WifiStatus> = callbackFlow {
170+
var wifiStatus: WifiStatus
171+
listener.start {
172+
// Update wifiStatus value accordingly with the new state
173+
wifiStatus = when (state) {
72174
WifiManager.WIFI_STATE_DISABLED, WifiManager.WIFI_STATE_DISABLING -> WifiStatus(
73-
WifiStatus.State.DISCONNECTED
175+
State.DISCONNECTED
74176
)
75177
WifiManager.WIFI_STATE_ENABLED -> WifiStatus(
76-
state = if (isFineLocationAccessGranted) WifiStatus.State.CONNECTED else WifiStatus.State.CONNECTED_MISSING_FINE_LOCATION_PERMISSION,
178+
state = if (isFineLocationAccessGranted) State.CONNECTED else State.CONNECTED_MISSING_FINE_LOCATION_PERMISSION,
77179
ssid = connectionInfo.ssid,
78180
bssid = connectionInfo.bssid,
79181
band = band,
80182
rssi = connectionInfo.rssi
81183
)
82-
WifiManager.WIFI_STATE_ENABLING -> WifiStatus(WifiStatus.State.ENABLING)
83-
else -> WifiStatus(WifiStatus.State.UNKNOWN)
184+
WifiManager.WIFI_STATE_ENABLING -> WifiStatus(State.ENABLING)
185+
else -> WifiStatus(State.UNKNOWN)
84186
}
85-
} else {
86-
WifiStatus(WifiStatus.State.UNKNOWN)
187+
channel.trySend(wifiStatus)
87188
}
189+
awaitClose {
190+
wifiStatus = WifiStatus(State.UNKNOWN)
191+
stop()
192+
}
193+
}
88194

89195
/**
90-
* True if the user granted access to [Manifest.permission.ACCESS_FINE_LOCATION]
196+
* Stop listening to Wi-Fi changes
91197
*/
92-
val isFineLocationAccessGranted: Boolean = ContextCompat.checkSelfPermission(
93-
context,
94-
Manifest.permission.ACCESS_FINE_LOCATION
95-
) == PERMISSION_GRANTED
198+
fun stop() = listener.stop()
96199
}

src/main/java/net/kuama/wifiMonitor/data/WifiStatus.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import net.kuama.wifiMonitor.WifiMonitor
66
/**
77
* Collects a set of useful properties about the WiFi
88
*/
9-
class WifiStatus(
9+
data class WifiStatus(
1010
/**
1111
* Connection state
1212
*/

src/test/java/net/kuama/wifiMonitor/ExampleUnitTest.kt

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)