mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-22 20:24:03 -04:00
Refactored the RxJava
code to use Kotlin Coroutines
in WebServerHelper
.
* Refactored the IP address polling logic using coroutine Flow. Now the IP check runs on the IO thread and the result is pushed to the main thread, because our callbacks need to work on the main thread. Also, this whole operation will automatically cancel if the user switches the screen or if the given coroutine scope gets cancelled, since the scope is lifecycle-aware. This helps to avoid using extra resources unnecessarily. * Added clear and concise method-level comments to improve code readability and help others easily understand the purpose and behavior of each method.
This commit is contained in:
parent
2819c187ef
commit
42a1c46e19
@ -17,9 +17,16 @@
|
||||
*/
|
||||
package org.kiwix.kiwixmobile.webserver
|
||||
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.timeout
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.utils.DEFAULT_PORT
|
||||
import org.kiwix.kiwixmobile.core.utils.ServerUtils
|
||||
@ -29,21 +36,24 @@ import org.kiwix.kiwixmobile.core.utils.ServerUtils.getIpAddress
|
||||
import org.kiwix.kiwixmobile.core.utils.files.Log
|
||||
import org.kiwix.kiwixmobile.webserver.wifi_hotspot.IpAddressCallbacks
|
||||
import org.kiwix.kiwixmobile.webserver.wifi_hotspot.ServerStatus
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* WebServerHelper class is used to set up the suitable environment i.e. getting the
|
||||
* ip address and port no. before starting the WebServer
|
||||
* Created by Adeel Zafar on 18/07/2019.
|
||||
*/
|
||||
|
||||
const val FINDING_IP_ADDRESS_TIMEOUT = 15_000L
|
||||
const val FINDING_IP_ADDRESS_RETRY_TIME = 1_000L
|
||||
|
||||
class WebServerHelper @Inject constructor(
|
||||
private val kiwixServerFactory: KiwixServer.Factory,
|
||||
private val ipAddressCallbacks: IpAddressCallbacks
|
||||
) {
|
||||
private var kiwixServer: KiwixServer? = null
|
||||
private var isServerStarted = false
|
||||
private var validIpAddressDisposable: Disposable? = null
|
||||
|
||||
suspend fun startServerHelper(
|
||||
selectedBooksPath: ArrayList<String>,
|
||||
@ -98,31 +108,53 @@ class WebServerHelper @Inject constructor(
|
||||
ServerUtils.isServerStarted = isStarted
|
||||
}
|
||||
|
||||
// Keeps checking if hotspot has been turned using the ip address with an interval of 1 sec
|
||||
// If no ip is found after 15 seconds, dismisses the progress dialog
|
||||
@Suppress("MagicNumber")
|
||||
fun pollForValidIpAddress() {
|
||||
validIpAddressDisposable =
|
||||
Flowable.interval(1, TimeUnit.SECONDS)
|
||||
.map { getIp() }
|
||||
.filter { s: String? -> s != INVALID_IP }
|
||||
.timeout(15, TimeUnit.SECONDS)
|
||||
.take(1)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ s: String? ->
|
||||
ipAddressCallbacks.onIpAddressValid()
|
||||
Log.d(TAG, "onSuccess: $s")
|
||||
}
|
||||
) { e: Throwable? ->
|
||||
Log.d(TAG, "Unable to turn on server", e)
|
||||
/**
|
||||
* Starts polling for a valid IP address using a [Flow].
|
||||
* - Polls every [FINDING_IP_ADDRESS_RETRY_TIME] milliseconds.
|
||||
* - If a valid IP is found, invokes [IpAddressCallbacks.onIpAddressInvalid].
|
||||
* - If no valid IP is found within [FINDING_IP_ADDRESS_TIMEOUT] seconds,
|
||||
* invokes [IpAddressCallbacks.onIpAddressInvalid].
|
||||
* - The flow runs on [Dispatchers.IO] and results are collected on the Main thread.
|
||||
* - Automatically cancels if [serviceScope] is cancelled (e.g. lifecycle aware).
|
||||
*/
|
||||
@OptIn(FlowPreview::class)
|
||||
@Suppress("InjectDispatcher")
|
||||
fun pollForValidIpAddress(serviceScope: CoroutineScope) {
|
||||
serviceScope.launch(Dispatchers.Main) {
|
||||
ipPollingFlow()
|
||||
.timeout(FINDING_IP_ADDRESS_TIMEOUT.seconds)
|
||||
.catch {
|
||||
Log.d(TAG, "Unable to turn on server", it)
|
||||
ipAddressCallbacks.onIpAddressInvalid()
|
||||
}.collect {
|
||||
ipAddressCallbacks.onIpAddressValid()
|
||||
Log.d(TAG, "onSuccess: $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
validIpAddressDisposable?.dispose()
|
||||
/**
|
||||
* Creates a [Flow] that emits the current IP address every [FINDING_IP_ADDRESS_RETRY_TIME] milliseconds.
|
||||
* - If the returned IP is not [INVALID_IP], the flow completes.
|
||||
* - The flow runs entirely on [Dispatchers.IO].
|
||||
*/
|
||||
@Suppress("InjectDispatcher", "TooGenericExceptionCaught")
|
||||
private fun ipPollingFlow(): Flow<String?> = flow {
|
||||
while (true) {
|
||||
// if ip address is not found wait for 1 second to again getting the ip address.
|
||||
// this is equivalent to our `rxJava` code.
|
||||
delay(FINDING_IP_ADDRESS_RETRY_TIME)
|
||||
val ip = try {
|
||||
getIp()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting IP address", e)
|
||||
INVALID_IP
|
||||
}
|
||||
emit(ip)
|
||||
|
||||
if (ip != INVALID_IP) break
|
||||
}
|
||||
}.flowOn(Dispatchers.IO)
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WebServerHelper"
|
||||
|
@ -24,6 +24,8 @@ import android.os.IBinder
|
||||
import android.widget.Toast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.kiwix.kiwixmobile.KiwixApp
|
||||
@ -53,6 +55,7 @@ class HotspotService :
|
||||
|
||||
@set:Inject
|
||||
var hotspotStateReceiver: HotspotStateReceiver? = null
|
||||
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
private var zimHostCallbacks: ZimHostCallbacks? = null
|
||||
private val serviceBinder: IBinder = HotspotBinder(this)
|
||||
@ -64,13 +67,13 @@ class HotspotService :
|
||||
.build()
|
||||
.inject(this)
|
||||
super.onCreate()
|
||||
hotspotStateReceiver?.let(this::registerReceiver)
|
||||
hotspotStateReceiver?.let(::registerReceiver)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
webServerHelper?.dispose()
|
||||
hotspotStateReceiver?.let(this@HotspotService::unregisterReceiver)
|
||||
hotspotStateReceiver?.let(::unregisterReceiver)
|
||||
super.onDestroy()
|
||||
serviceScope.cancel()
|
||||
}
|
||||
|
||||
@Suppress("NestedBlockDepth", "InjectDispatcher")
|
||||
@ -79,7 +82,7 @@ class HotspotService :
|
||||
ACTION_START_SERVER -> {
|
||||
val restartServer = intent.getBooleanExtra(RESTART_SERVER, false)
|
||||
intent.getStringArrayListExtra(ZimHostFragment.SELECTED_ZIM_PATHS_KEY)?.let {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
serviceScope.launch {
|
||||
val serverStatus =
|
||||
withContext(Dispatchers.IO) {
|
||||
webServerHelper?.startServerHelper(it, restartServer)
|
||||
@ -109,7 +112,7 @@ class HotspotService :
|
||||
stopHotspotAndDismissNotification()
|
||||
}
|
||||
|
||||
ACTION_CHECK_IP_ADDRESS -> webServerHelper?.pollForValidIpAddress()
|
||||
ACTION_CHECK_IP_ADDRESS -> webServerHelper?.pollForValidIpAddress(serviceScope)
|
||||
else -> {}
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
|
Loading…
x
Reference in New Issue
Block a user