/* * Copyright 2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:Suppress("unused") package org.jetbrains.anko import android.app.Activity import android.content.Context import android.os.Handler import android.os.Looper import androidx.fragment.app.Fragment import java.lang.ref.WeakReference import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.Future /** * Execute [f] on the application UI thread. */ fun Context.runOnUiThread(f: Context.() -> Unit) { if (Looper.getMainLooper() === Looper.myLooper()) f() else ContextHelper.handler.post { f() } } /** * Execute [f] on the application UI thread. */ inline fun Fragment.runOnUiThread(crossinline f: () -> Unit) { requireContext().runOnUiThread { f() } } class AnkoAsyncContext(val weakRef: WeakReference) /** * Execute [f] on the application UI thread. * If the [doAsync] receiver still exists (was not collected by GC), * [f] gets it as a parameter ([f] gets null if the receiver does not exist anymore). */ fun AnkoAsyncContext.onComplete(f: (T?) -> Unit) { val ref = weakRef.get() if (Looper.getMainLooper() === Looper.myLooper()) { f(ref) } else { ContextHelper.handler.post { f(ref) } } } /** * Execute [f] on the application UI thread. * [doAsync] receiver will be passed to [f]. * If the receiver does not exist anymore (it was collected by GC), [f] will not be executed. */ fun AnkoAsyncContext.uiThread(f: (T) -> Unit): Boolean { val ref = weakRef.get() ?: return false if (Looper.getMainLooper() === Looper.myLooper()) { f(ref) } else { ContextHelper.handler.post { f(ref) } } return true } /** * Execute [f] on the application UI thread if the underlying [Activity] still exists and is not finished. * The receiver [Activity] will be passed to [f]. * If it is not exist anymore or if it was finished, [f] will not be called. */ fun AnkoAsyncContext.activityUiThread(f: (T) -> Unit): Boolean { val activity = weakRef.get() ?: return false if (activity.isFinishing) return false activity.runOnUiThread { f(activity) } return true } fun AnkoAsyncContext.activityUiThreadWithContext(f: Context.(T) -> Unit): Boolean { val activity = weakRef.get() ?: return false if (activity.isFinishing) return false activity.runOnUiThread { activity.f(activity) } return true } @JvmName("activityContextUiThread") fun AnkoAsyncContext>.activityUiThread(f: (T) -> Unit): Boolean { val activity = weakRef.get()?.owner ?: return false if (activity.isFinishing) return false activity.runOnUiThread { f(activity) } return true } @JvmName("activityContextUiThreadWithContext") fun AnkoAsyncContext>.activityUiThreadWithContext(f: Context.(T) -> Unit): Boolean { val activity = weakRef.get()?.owner ?: return false if (activity.isFinishing) return false activity.runOnUiThread { activity.f(activity) } return true } @Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.") fun AnkoAsyncContext.fragmentUiThread(f: (T) -> Unit): Boolean { val fragment = weakRef.get() ?: return false if (fragment.isDetached) return false val activity = fragment.activity ?: return false activity.runOnUiThread { f(fragment) } return true } @Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.") fun AnkoAsyncContext.fragmentUiThreadWithContext(f: Context.(T) -> Unit): Boolean { val fragment = weakRef.get() ?: return false if (fragment.isDetached) return false val activity = fragment.activity ?: return false activity.runOnUiThread { activity.f(fragment) } return true } private val crashLogger = { throwable: Throwable -> throwable.printStackTrace() } /** * Execute [task] asynchronously. * * @param exceptionHandler optional exception handler. * If defined, any exceptions thrown inside [task] will be passed to it. If not, exceptions will be ignored. * @param task the code to execute asynchronously. */ fun T.doAsync( exceptionHandler: ((Throwable) -> Unit)? = crashLogger, task: AnkoAsyncContext.() -> Unit, ): Future { val context = AnkoAsyncContext(WeakReference(this)) return BackgroundExecutor.submit { return@submit try { context.task() } catch (thr: Throwable) { val result = exceptionHandler?.invoke(thr) result ?: Unit } } } fun T.doAsync( exceptionHandler: ((Throwable) -> Unit)? = crashLogger, executorService: ExecutorService, task: AnkoAsyncContext.() -> Unit, ): Future { val context = AnkoAsyncContext(WeakReference(this)) return executorService.submit { try { context.task() } catch (thr: Throwable) { exceptionHandler?.invoke(thr) } } } fun T.doAsyncResult( exceptionHandler: ((Throwable) -> Unit)? = crashLogger, task: AnkoAsyncContext.() -> R, ): Future { val context = AnkoAsyncContext(WeakReference(this)) return BackgroundExecutor.submit { try { context.task() } catch (thr: Throwable) { exceptionHandler?.invoke(thr) throw thr } } } fun T.doAsyncResult( exceptionHandler: ((Throwable) -> Unit)? = crashLogger, executorService: ExecutorService, task: AnkoAsyncContext.() -> R, ): Future { val context = AnkoAsyncContext(WeakReference(this)) return executorService.submit { try { context.task() } catch (thr: Throwable) { exceptionHandler?.invoke(thr) throw thr } } } internal object BackgroundExecutor { @Suppress("MemberVisibilityCanBePrivate") var executor: ExecutorService = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors()) fun submit(task: () -> T): Future = executor.submit(task) } private object ContextHelper { val handler = Handler(Looper.getMainLooper()) }