Having a suspend function fetchData(). What it does is to launch a few jobs in the withContext, so that it will only return after the jobs are complete (which are: suspend fun getData(): Boolean).
And also want if it times out then return false from the function.
The problem is when it times out, with withTimeoutOrNull(500) { jobs.joinAll() }, it stuck in the function not exit.
The log shows it times out, also clearly points to last line of the code before exit the function:
E/+++: +++ in fetchData() after null = withTimeoutOrNull(500), jobs.sizs: 3
E/+++: +++ --- exit fetchData(), allFresh: false
But the caller of the fetchData() gets stuck and not return from the fetchData().
This is the caller:
suspend fun caller() {
var allGood = fetchData()
// never return to here
Log.e("+++", "+++ caller(), after allGood: $allGood = fetchData()")
...
}
Below is the code, how to cancel the jobs if timeout?
suspend fun fetchData(): Boolean = withContext(Dispatchers.IO) {
var allFresh = requestHandlertMap.size > 0
if (!allFresh) {
allFresh
} else {
val handlers = requestHandlertMap.values.toList()
val jobs: List<Deferred<Boolean>> = handlers.map {handler->
async(start = CoroutineStart.LAZY) {
if (isActive) handler.getData() else true
.also {
Log.e("+++", "+++ in fetchData():async{} after handler.getData()")
}
}
}
val result = withTimeoutOrNull(500) { jobs.joinAll() }
Log.e("+++", "+++ in fetchData() after $result = withTimeoutOrNull(500), jobs.size: ${jobs.size} ")
if (result != null) {
allFresh = jobs.all { deferred ->
deferred.await()
}
Log.e("+++", "+++ +++ +++ in fetchData() call onDataReady(), allFresh: $allFresh = deferred.await() ")
onDataReady()
} else {
// how to cancel the jobs ???
//jobs.all { deferred ->
//deferred.cancelChildren()
//}
allFresh = false
}
allFresh
.also {
Log.e("+++", "+++ --- exit fetchData(), allFresh: $allFresh ")
}
}
}
After some reading/trying, it seems having a few issues with the implementation.
Somehow the CoroutineStart.LAZY causes a strange behavior, that the async(start = CoroutineStart.LAZY)
start sequentially (expecting they should start to be concurrently), so that when it times out it stuck in the function (guess because it is wrapped in the withContext(Dispatchers.IO) and not all child coroutines are completed -- if there is someone not start yet).
Remove the start = CoroutineStart.LAZY makes it returning from the fun fetchData()
val jobs: List<Deferred<Boolean>> = handlers.map {handler->
async(start = CoroutineStart.LAZY) {
if (isActive) handler.getData() else true
.also {
Log.e("+++", "+++ in fetchData():async{} after handler.getData()")
}
}
}
the suspend fun getData(): Boolean was not implemented cooperate to be cancellable, which may causes it still stay in the function until all children are completed although timeout has already happened.
seems it still need to call the deferred.cancelChildren(), otherwise they are not cancelled by the withTimeoutNotNull(), not sure why, isnt it supposed to cancel the jobs automatically?
the change made
private suspend fun fetchData(): Boolean {
var allFresh: Boolean? = requestHandlertMap.size > 0
if (allFresh == true) {
val handlers = requestHandlertMap.values.toList()
val jobs: List<Deferred<Boolean>> = handlers.map {
serviceScope.async(start = CoroutineStart.DEFAULT) { handler -> if (isActive) handler.getData() else false }
}
allFresh = withTimeoutOrNull(3000) {
try {
jobs.awaitAll().all { it }
} catch (ex: Throwable) {
false
}
}
if (allFresh != null) {
onDataReady()
} else {
jobs.map { deferred -> deferred.cancelChildren() }
}
}
return allFresh == true // allFresh = {null, true, false}
}
ref: here and here
Related
I would like to make simple widget for my weather app, to show local temperature. My question is: how to get the LocationTracker in my widget class?
class Widget: GlanceAppWidget() {...}
I found the solution, I use fusedLocationProviderClient
//client
val locationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
So, my method get location:
> //get location
private suspend fun getCurrentLocation(
context: Context,
locationClient: FusedLocationProviderClient
): Location? {
val hasAccessFineLocationPermission = ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
val hasAccessCoarseLocationPermission = ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) ||
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
if (!hasAccessCoarseLocationPermission || !hasAccessFineLocationPermission || !isGpsEnabled) {
return null
}
return suspendCancellableCoroutine { cont ->
locationClient.lastLocation.apply {
if (isComplete) {
if (isSuccessful) {
cont.resume(result)
} else {
cont.resume(null)
}
return#suspendCancellableCoroutine
}
addOnSuccessListener {
cont.resume(it)
}
addOnFailureListener {
cont.resume(null)
}
addOnCanceledListener {
cont.cancel()
}
}
}
}
What's the most performant way in Kotlin to allow concurrent file I/O in multi-reader, single-writer fashion?
I have the below, but I'm unsure how much overhead is being created by the coroutine facilities:
AsyncFileChannel, with extension functions to use it in a suspend context
Taken from example here: https://github.com/Kotlin/coroutines-examples/blob/master/examples/io/io.kt
DiskManager class, that uses a custom ReadWriteMutex
Searching for examples of this doesn't turn up much (try searching Github for ReadWriteMutex, there are a tiny handful of Kotlin repos implementing this).
class DiskManagerImpl(file: File) : DiskManager {
private val mutex = ReadWriteMutexImpl()
private val channel = AsynchronousFileChannel.open(file.toPath(),
StandardOpenOption.READ, StandardOpenOption.WRITE)
override suspend fun readPage(pageId: PageId, buffer: MemorySegment) = withContext(Dispatchers.IO) {
mutex.withReadLock {
val offset = pageId * PAGE_SIZE
val bytesRead = channel.readAsync(buffer.asByteBuffer(), offset.toLong())
require(bytesRead == PAGE_SIZE) { "Failed to read page $pageId" }
}
}
override suspend fun writePage(pageId: PageId, buffer: MemorySegment) = withContext(Dispatchers.IO) {
mutex.withWriteLock {
val offset = pageId * PAGE_SIZE
val bytesWritten = channel.writeAsync(buffer.asByteBuffer(), offset.toLong())
require(bytesWritten == PAGE_SIZE) { "Failed to write page $pageId" }
}
}
}
class ReadWriteMutexImpl : ReadWriteMutex {
private val read = Mutex()
private val write = Mutex()
private val readers = atomic(0)
override suspend fun lockRead() {
if (readers.getAndIncrement() == 0) {
read.lock()
}
}
override fun unlockRead() {
if (readers.decrementAndGet() == 0) {
read.unlock()
}
}
override suspend fun lockWrite() {
read.lock()
write.lock()
}
override fun unlockWrite() {
write.unlock()
read.unlock()
}
}
suspend inline fun <T> ReadWriteMutex.withReadLock(block: () -> T): T {
lockRead()
return try {
block()
} finally {
unlockRead()
}
}
suspend inline fun <T> ReadWriteMutex.withWriteLock(block: () -> T): T {
lockWrite()
return try {
block()
} finally {
unlockWrite()
}
}
I have a set up, using Ashley Mills Reachability, which is supposed to send a notification, using NotificationCenter, to the application when the connection of the app changes. It is set up as follows:
func reachabilityChanged(note: Notification) {
let reachability = note.object as! Reachability
switch reachability.connection {
case .wifi:
print("Reachable via WiFi")
case .cellular:
print("Reachable via Cellular")
case .none:
print("Network not reachable")
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.reachabilityChanged), name: .reachabilityChanged, object: reachability)
do{
try reachability.startNotifier()
}catch{
print("could not start reachability notifier")
}
}
With my computer (which is what I am running the simulator on) connected to wifi, the console accurately prints "Reachable via Wifi". Turning off the wifi causes the console to, again, accurately print "Network not reachable". I run into an issue, though, when I turn the wifi back on. "Reachable via Wifi" does not get printed to the log... in fact, nothing gets printed to the log. I do not know why this is happening nor how to fix it. Anyone have any ideas?
This is the reachability code:
import SystemConfiguration
import Foundation
public enum ReachabilityError: Error {
case FailedToCreateWithAddress(sockaddr_in)
case FailedToCreateWithHostname(String)
case UnableToSetCallback
case UnableToSetDispatchQueue
}
#available(*, unavailable, renamed: "Notification.Name.reachabilityChanged")
public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")
extension Notification.Name {
public static let reachabilityChanged = Notification.Name("reachabilityChanged")
}
func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
guard let info = info else { return }
let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
reachability.reachabilityChanged()
}
public class Reachability {
public typealias NetworkReachable = (Reachability) -> ()
public typealias NetworkUnreachable = (Reachability) -> ()
#available(*, unavailable, renamed: "Conection")
public enum NetworkStatus: CustomStringConvertible {
case notReachable, reachableViaWiFi, reachableViaWWAN
public var description: String {
switch self {
case .reachableViaWWAN: return "Cellular"
case .reachableViaWiFi: return "WiFi"
case .notReachable: return "No Connection"
}
}
}
public enum Connection: CustomStringConvertible {
case none, wifi, cellular
public var description: String {
switch self {
case .cellular: return "Cellular"
case .wifi: return "WiFi"
case .none: return "No Connection"
}
}
}
public var whenReachable: NetworkReachable?
public var whenUnreachable: NetworkUnreachable?
#available(*, deprecated: 4.0, renamed: "allowsCellularConnection")
public let reachableOnWWAN: Bool = true
/// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
public var allowsCellularConnection: Bool
// The notification center on which "reachability changed" events are being posted
public var notificationCenter: NotificationCenter = NotificationCenter.default
#available(*, deprecated: 4.0, renamed: "connection.description")
public var currentReachabilityString: String {
return "\(connection)"
}
#available(*, unavailable, renamed: "connection")
public var currentReachabilityStatus: Connection {
return connection
}
public var connection: Connection {
guard isReachableFlagSet else { return .none }
// If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
guard isRunningOnDevice else { return .wifi }
var connection = Connection.none
if !isConnectionRequiredFlagSet {
connection = .wifi
}
if isConnectionOnTrafficOrDemandFlagSet {
if !isInterventionRequiredFlagSet {
connection = .wifi
}
}
if isOnWWANFlagSet {
if !allowsCellularConnection {
connection = .none
} else {
connection = .cellular
}
}
return connection
}
fileprivate var previousFlags: SCNetworkReachabilityFlags?
fileprivate var isRunningOnDevice: Bool = {
#if (arch(i386) || arch(x86_64)) && os(iOS)
return false
#else
return true
#endif
}()
fileprivate var notifierRunning = false
fileprivate let reachabilityRef: SCNetworkReachability
fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability")
required public init(reachabilityRef: SCNetworkReachability) {
allowsCellularConnection = true
self.reachabilityRef = reachabilityRef
}
public convenience init?(hostname: String) {
guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil }
self.init(reachabilityRef: ref)
}
public convenience init?() {
var zeroAddress = sockaddr()
zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
zeroAddress.sa_family = sa_family_t(AF_INET)
guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { return nil }
self.init(reachabilityRef: ref)
}
deinit {
stopNotifier()
}
}
public extension Reachability {
// MARK: - *** Notifier methods ***
func startNotifier() throws {
guard !notifierRunning else { return }
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque())
if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
stopNotifier()
throw ReachabilityError.UnableToSetCallback
}
if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
stopNotifier()
throw ReachabilityError.UnableToSetDispatchQueue
}
// Perform an initial check
reachabilitySerialQueue.async {
self.reachabilityChanged()
}
notifierRunning = true
}
func stopNotifier() {
defer { notifierRunning = false }
SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
}
// MARK: - *** Connection test methods ***
#available(*, deprecated: 4.0, message: "Please use `connection != .none`")
var isReachable: Bool {
guard isReachableFlagSet else { return false }
if isConnectionRequiredAndTransientFlagSet {
return false
}
if isRunningOnDevice {
if isOnWWANFlagSet && !reachableOnWWAN {
// We don't want to connect when on cellular connection
return false
}
}
return true
}
#available(*, deprecated: 4.0, message: "Please use `connection == .cellular`")
var isReachableViaWWAN: Bool {
// Check we're not on the simulator, we're REACHABLE and check we're on WWAN
return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet
}
#available(*, deprecated: 4.0, message: "Please use `connection == .wifi`")
var isReachableViaWiFi: Bool {
// Check we're reachable
guard isReachableFlagSet else { return false }
// If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
guard isRunningOnDevice else { return true }
// Check we're NOT on WWAN
return !isOnWWANFlagSet
}
var description: String {
let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X"
let R = isReachableFlagSet ? "R" : "-"
let c = isConnectionRequiredFlagSet ? "c" : "-"
let t = isTransientConnectionFlagSet ? "t" : "-"
let i = isInterventionRequiredFlagSet ? "i" : "-"
let C = isConnectionOnTrafficFlagSet ? "C" : "-"
let D = isConnectionOnDemandFlagSet ? "D" : "-"
let l = isLocalAddressFlagSet ? "l" : "-"
let d = isDirectFlagSet ? "d" : "-"
return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
}
}
fileprivate extension Reachability {
func reachabilityChanged() {
guard previousFlags != flags else { return }
let block = connection != .none ? whenReachable : whenUnreachable
DispatchQueue.main.async {
block?(self)
self.notificationCenter.post(name: .reachabilityChanged, object:self)
}
previousFlags = flags
}
var isOnWWANFlagSet: Bool {
#if os(iOS)
return flags.contains(.isWWAN)
#else
return false
#endif
}
var isReachableFlagSet: Bool {
return flags.contains(.reachable)
}
var isConnectionRequiredFlagSet: Bool {
return flags.contains(.connectionRequired)
}
var isInterventionRequiredFlagSet: Bool {
return flags.contains(.interventionRequired)
}
var isConnectionOnTrafficFlagSet: Bool {
return flags.contains(.connectionOnTraffic)
}
var isConnectionOnDemandFlagSet: Bool {
return flags.contains(.connectionOnDemand)
}
var isConnectionOnTrafficOrDemandFlagSet: Bool {
return !flags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
}
var isTransientConnectionFlagSet: Bool {
return flags.contains(.transientConnection)
}
var isLocalAddressFlagSet: Bool {
return flags.contains(.isLocalAddress)
}
var isDirectFlagSet: Bool {
return flags.contains(.isDirect)
}
var isConnectionRequiredAndTransientFlagSet: Bool {
return flags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
}
var flags: SCNetworkReachabilityFlags {
var flags = SCNetworkReachabilityFlags()
if SCNetworkReachabilityGetFlags(reachabilityRef, &flags) {
return flags
} else {
return SCNetworkReachabilityFlags()
}
}
}
it get's triggered only for one time per run in the simulator, but on a real iOS device, it works normally.
I'm wondering if exists a better way to complete a worker subject. To do this, a complete when all tasks have run.
Here is the code:
export class WorkerSubject<T> extends Subject<T> {
protected worker: Worker;
protected jobs = 0;
protected finish = false;
constructor(callback: Function, async = false) {
super();
this.worker =
async ? createWorkerPostMessage(callback) : createStaticWorker(callback);
this.worker.onmessage = e => {
this.jobs--;
super.next(e.data);
if (this.jobs === 0 && this.finish) {
super.complete();
this.worker.terminate();
}
};
this.worker.onerror = e => {
super.error(e);
this.worker.terminate();
};
}
next(value: any): void {
if (this.finish) {
throw new Error('WorkerSubject complete, no more values can be emited.')
}
this.jobs++;
this.worker.postMessage(value);
}
complete() {
this.finish = true;
}
}
Any idea to improve this?
developed a webview app, I have an option to upload image (input type = "file"). In the browser functions normally, but within the webview, it does not. I would like some help to resolve this problem.
because you not post any code, take a look of my code.
It allow webview to upload image from camera or galery
class HandlingWebview(){
private var mFilePathCallback: ValueCallback<Array<Uri>>? = null
private var mCameraPhotoPath: String? = null
companion object {
const val CHOOSE_FILE_REQUEST_CODE = 9685
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
initObserver()
initView()
}
private val permissionUtils: PermissionUtils by lazy {
PermissionUtils(
this,
trackingService,
getString(R.string.rationale_storage),
Constant.RC_PERMISSIONS_DOWNLOAD_DOCS,
Constant.PERMISSIONS_DOWNLOAD_DOCS
)
}
private fun initView() {
binding.webView.settings.apply {
javaScriptEnabled = true
loadWithOverviewMode = true
useWideViewPort = true
domStorageEnabled = true
}
binding.webView.setOnKeyListener(object : View.OnKeyListener {
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
if (event?.action == KeyEvent.ACTION_DOWN) {
val webView = v as WebView
when (keyCode) {
KeyEvent.KEYCODE_BACK -> if (webView.canGoBack()) {
webView.goBack()
return true
}
}
}
return false
}
})
binding.webView.apply {
loadUrl(url)
}
binding.webView.webViewClient = object : WebViewClient() {
override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
super.doUpdateVisitedHistory(view, url, isReload)
}
}
binding.webView.webChromeClient = object : WebChromeClient() {
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
if (permissionUtils.isAllPermissionAllowed()) {
// Double check that we don't have any existing callbacks
startActivityChooser(fileChooserParams, filePathCallback)
} else observePermissionResult(permissionUtils.build().asLiveData(), fileChooserParams, filePathCallback)
return true
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) = permissionUtils.onRequestPermissionsResult(requestCode, permissions, grantResults)
private fun startActivityChooser(
fileChooserParams: WebChromeClient.FileChooserParams?,
filePathCallback: ValueCallback<Array<Uri>>?
) {
mFilePathCallback?.onReceiveValue(null)
mFilePathCallback = filePathCallback
activity?.packageManager?.let {
var takePictureIntent: Intent? = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (takePictureIntent?.resolveActivity(it) != null){
var photoFile: File? = null
try {
photoFile = createImageFile()
takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath)
} catch (ex: IOException) {
// Error occurred while creating the File
Timber.i("Unable to create Image File $ex")
}
// Continue only if the File was successfully created
if (photoFile != null) {
mCameraPhotoPath = "file:" + photoFile.absolutePath
takePictureIntent.putExtra(
MediaStore.EXTRA_OUTPUT,
Uri.fromFile(photoFile)
)
} else {
takePictureIntent = null
}
}
val contentSelectionIntent = Intent(Intent.ACTION_GET_CONTENT)
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE)
contentSelectionIntent.type = "image/*"
val intentArray: Array<Intent?> = takePictureIntent?.let { arrayOf(it) } ?: arrayOfNulls(0)
val chooserIntent = Intent(Intent.ACTION_CHOOSER)
chooserIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser")
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)
startActivityForResult(chooserIntent, CHOOSE_FILE_REQUEST_CODE)
}
}
private fun initObserver() {}
private fun observePermissionResult(
permissionResult: LiveData<Event<PermissionUtils.Companion.PermissionResult>>,
fileChooserParams: WebChromeClient.FileChooserParams?,
filePathCallback: ValueCallback<Array<Uri>>?
) {
permissionResult.observe(viewLifecycleOwner) { event ->
event?.getContentIfNotHandled()?.let {
when (it) {
is PermissionUtils.Companion.PermissionResult.Denied -> {
// pass
}
is PermissionUtils.Companion.PermissionResult.Granted -> {
// pass
}
is PermissionUtils.Companion.PermissionResult.AllGranted -> {
startActivityChooser(fileChooserParams, filePathCallback)
}
}
}
}
}
override fun useCustomBackEvent(): Boolean = true
override fun onBackEvent() {
destroyWebView()
super.onBackEvent()
}
private fun destroyWebView() {
binding.llParent.removeAllViews()
binding.webView.apply {
clearHistory()
clearCache(true)
onPause()
removeAllViews()
destroyDrawingCache()
destroy()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
CHOOSE_FILE_REQUEST_CODE -> {
var results: Array<Uri>? = null
if (resultCode == Activity.RESULT_OK && mFilePathCallback!= null) {
if (data == null) { // take photo from camera
if (mCameraPhotoPath != null) results = arrayOf(Uri.parse(mCameraPhotoPath))
} else { // image picker
data.dataString?.let { results = arrayOf(Uri.parse(it)) }
}
}
mFilePathCallback?.onReceiveValue(results)
mFilePathCallback = null
}
}
super.onActivityResult(requestCode, resultCode, data)
}
#Throws(IOException::class)
private fun createImageFile(): File? {
// Create an image file name
val timeStamp: String = getTodayDateString()
val imageFileName = "JPEG_" + timeStamp + "_"
val storageDir: File = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES
)
return File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
)
}
}