What I'm trying to do is the following:
- Load an Agent icon from an API
- Using Palette, determine what is the vibrant swatch
- Set this background RGB color and save it into the database
- Database will be updated, updating the flow and pushing the new updates to the UI
- UI picks it up, which then gets set into a recycler view
However, I'm struggling to get it working in my repository. My function looks like this:
suspend fun updateAgentBackground(agent: Agent, successResult: SuccessResult) {
if (agent.backgroundRgb == null) {
withContext(Dispatchers.IO) {
Palette.Builder(successResult.drawable.toBitmap()).generate { palette ->
val rgb = palette?.vibrantSwatch?.rgb
if (rgb != null) {
val agentWithBackground = agent.copy(backgroundRgb = rgb)
agentDao.insertAgent(agentWithBackground.toAgentEntity())
}
}
}
}
}
However, it crashes at runtime on agentDao.insertAgent()
because it says it's running the DB operation on the main thread. I also cannot wrap this method within the generate()
callback with withContext(Dispatchers.IO)
since it's not in a coroutine body.
This is what the Palette.Builder().generate()
function looks like in the source code:
@NonNull
public AsyncTask<Bitmap, Void, Palette> generate(
@NonNull final PaletteAsyncListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener can not be null");
}
return new AsyncTask<Bitmap, Void, Palette>() {
@Override
@Nullable
protected Palette doInBackground(Bitmap... params) {
try {
return generate();
} catch (Exception e) {
Log.e(LOG_TAG, "Exception thrown during async generate", e);
return null;
}
}
@Override
protected void onPostExecute(@Nullable Palette colorExtractor) {
listener.onGenerated(colorExtractor);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mBitmap);
}
public interface PaletteAsyncListener {
void onGenerated(@Nullable Palette palette);
}
So I'm passing it the PaletteAsyncListener
which just has one function onGenerated()
. I somehow want this method to suspend the coroutine, returning to me the Agent
with the updated background. Or somehow to be able to call my DB method agentDao.insertAgent()
within this callback off the main thread.
Can I somehow mix and match how the source code is using AsyncTask
with my coroutines?
CodePudding user response:
AsyncTask
was deprecated in API level 30, consider using Kotlin Coroutines instead or other Kotlin concurrency utilities. Without AsyncTask
it will look something like this:
In Palette.Builder
class:
suspend fun generatePalette() = withContext(Dispatchers.IO) {
generate()
}
In repository:
suspend fun updateAgentBackground(agent: Agent, successResult: SuccessResult) {
if (agent.backgroundRgb == null) {
val palette = Palette.Builder(successResult.drawable.toBitmap()).generatePalette()
val rgb = palette?.vibrantSwatch?.rgb
if (rgb != null) {
val agentWithBackground = agent.copy(backgroundRgb = rgb)
agentDao.insertAgent(agentWithBackground.toAgentEntity())
}
}
}
Also in the AgentDao
mark function insertAgent
as suspend
:
suspend fun insertAgent(...)
CodePudding user response:
If for some reason you cannot modify the source code of the Palette.Builder class, you could write a suspending adapting function like this. suspendCancellableCoroutine
is for converting callback-based functions into suspend functions by suspending the coroutine and giving you the suspended continuation to work with directly. Resume the coroutine when the callback fires, and cancel the work if the coroutine is cancelled before it resumed.
suspend fun Palette.Builder.await(): Palette? = suspendCancellableCoroutine { cont ->
val task = generate { cont.resume(it) }
cont.invokeOnCancellation(task.cancel())
}
Then your other function can be written in a synchronous way and use withContext
where necessary:
suspend fun updateAgentBackground(agent: Agent, successResult: SuccessResult) {
if (agent.backgroundRgb == null) {
val palette = Palette.Builder(successResult.drawable.toBitmap()).await()
val rgb = palette?.vibrantSwatch?.rgb
if (rgb != null) {
val agentWithBackground = agent.copy(backgroundRgb = rgb)
agentDao.insertAgent(agentWithBackground.toAgentEntity())
}
}
}
Make sure you mark AgentDao.insertAgent()
as a suspend
function so you can call if anywhere in a coroutine without worrying about calling thread.
Above code assumes toBitmap()
is non-blocking. If it blocks, you should wrap it in an appropriate withContext()
.