Parallel api call threading using CoroutineWorker in kotlin android

A CoroutineWorker in Android is a part of the Android WorkManager library that allows you to perform background tasks in a structured and reliable way, leveraging Kotlin's coroutines for concurrency. You can use CoroutineWorker to execute tasks that require network calls, database operations, or any other asynchronous work while ensuring compatibility with Android's background execution constraints.

Here's a step-by-step guide on how to make parallel API calls in a CoroutineWorker in Android using Kotlin, you can use Kotlin's async and await functions from the kotlinx.coroutines library. Here's a step-by-step guide on how to do this:

  1. Add the kotlinx.coroutines library to your app's dependencies and ensure you have the WorkManager dependency added to your app's build.gradle file. If it's not already there, you can do this by adding the following line to your app's build.gradle file:

build.gradle (app level)
 
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0" implementation("androidx.work:work-runtime-ktx:2.7.1") }
  1. Create a CoroutineWorker class that will perform parallel API calls. Extend CoroutineWorker and override the doWork method.
kotlin code snippet
import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope class MyWorker( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { override suspend fun doWork(): Result = coroutineScope { try { // Create a list of async tasks for parallel API calls val apiCall1 = async { performApiCall1() } val apiCall2 = async { performApiCall2() } // Wait for all async tasks to complete val results = awaitAll(apiCall1, apiCall2) // Handle the results as needed val apiCall1Result = results[0] val apiCall2Result = results[1] // Process the results and do other work here // Return the success result Result.success() } catch (e: Exception) { // Handle errors here and return a failure result Result.failure() } } private suspend fun performApiCall1(): SomeResult { // Implement your API call logic here // Return the result of the API call } private suspend fun performApiCall2(): SomeResult { // Implement your second API call logic here // Return the result of the API call } }

In the code above:

  • We create two asynchronous tasks (apiCall1 and apiCall2) using the async function, each representing an API call.
  • We use awaitAll to wait for all async tasks to complete.
  • You can handle the API call results as needed and return a success or failure result.
  1. Finally, schedule the MyWorker to run using WorkManager as you normally would with any other Worker class.
kotlin code snippet:
val workRequest = OneTimeWorkRequest.Builder(MyWorker::class.java).build() WorkManager.getInstance(context).enqueue(workRequest)

This example demonstrates how to make parallel API calls within a CoroutineWorker. You can customize it according to your specific API call requirements and error handling.

Migrate Android gradle from Groovy Scripts to KTS (Kotlin script)

This section will guide your step-by-step to convert or migrate your Android project's Gradle build script from Groovy script to Kotlin DSL

 When we create a project in Android (Arctic Fox 2020.3.1), We get the default Gradle Script setup for us which has 3 gradle file written in Groovy script

These 3 Gradle files are:
    1. The settings.gradle
    2. The root project’s build.gradle
    2. The app module’s build.gradle

Convert the settings.gradle file
1. Rename settings.gradle to settings.gradle.kts

You can do so by selecting the settings.gradle file, then go to Refactor
→ Rename File.

Then, just convert it to settings.gradle.kts. 

2. Fix the file syntax for “include”

You just need to change

include ':app'
to

include("app")

settings.gradle example file in groovy script:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        jcenter() // Warning: this repository is going to shut down soon
        maven {
            url 'https://jitpack.io'
        }
        maven {
            url 'https://maven.google.com'
        }
        maven {
            url 'https://maven.fpregistry.io/releases'
        }
        maven {
            url "https://oss.sonatype.org/content/repositories/snapshots/"
        }
    }
}
rootProject.name = "Project name text"
include ':app'

'include ':news'
include ':events'
include ':home'
include ':profile'
include ':common'

example file becomes settings.gradle.kts in Kotlin script:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        jcenter() // Warning: this repository is going to shut down soon
        maven(url = "https://jitpack.io")
        maven(url = "https://maven.google.com")
        maven(url = "https://maven.fpregistry.io/releases")    
        maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")    
    }
}
rootProject.name = "Be better"
include("app")
include("news")
include("events")
include("home")
include("profile")
include("common")

How can I uncheck or reset the radio button in android/kotlin ?

I recently had the need to have a radio group where the selected item could be deselected by tapping it again. So, here I can be able to deselect all options inside anRadioGroup. I tried using click listeners but I couldn't accomplish. Then I am able to do it using a custom AppCompatRadioButton class in Kotlin as shown below: 
package com.myapp

import android.content.Context
import android.util.AttributeSet
import android.widget.RadioGroup
import androidx.appcompat.widget.AppCompatRadioButton

/**
* Created this class for custom radio button which toggles on click
*/
class ToggleAbleRadioButton(context: Context?, attrs: AttributeSet?) :
AppCompatRadioButton(context, attrs) {

override fun toggle() {
if (isChecked) {
if (parent is RadioGroup) {
(parent as RadioGroup).clearCheck()
}
} else {
isChecked = true
}
}
}

 You can use it inside radio group as shown below:

<RadioGroup
android:id="@+id/radip_group_whether_conditions"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="@id/guidelinesend"
app:layout_constraintStart_toStartOf="@id/guidelinestart"
app:layout_constraintTop_toBottomOf="@id/til_wind_direction_and_speed">

<com.myoctans.app.daily_reports.view.ToggleAbleRadioButton
android:id="@+id/mcb_sunny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sunny" />

<com.myoctans.app.daily_reports.view.ToggleAbleRadioButton
android:id="@+id/mcb_cloudy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cloudy" />

<com.myoctans.app.daily_reports.view.ToggleAbleRadioButton
android:id="@+id/mcb_rain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rain" />

<com.myoctans.app.daily_reports.view.ToggleAbleRadioButton
android:id="@+id/mcb_snow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/snow" />
</RadioGroup>

You can refer this link for more insight: Android radio button uncheck,
how to uncheck radio button in android programmatically code example

Android Kotlin Room database, build error “A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution” on Android Studio Preview Apple M1

Component used: Room

Version used: 2.3.0

Devices/Android versions reproduced on: MacBook Pro M1 chip


Build failed on Apple M1 with Room database 2.3.0

Execution failed for task ':app:kaptDebugKotlin'.

> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction

   > java.lang.reflect.InvocationTargetException (no error message)

* Try:

Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

Solution:

  implementation "androidx.room:room-runtime:2.3.0"

  annotationProcessor "androidx.room:room-compiler:2.3.0"

1. You can use Alpha Release version of room: 

implementation "androidx.room:room-runtime:2.4.0-alpha04"

annotationProcessor "androidx.room:room-compiler:2.4.0-alpha04"

Room Database Alpha Release version 2.4.0-alpha04 

2. Also updated project level build Gradle with latest Kotlin plugin version and latest Gradle plugin. (Need to update this because I was facing some error related to apply plugin: 'kotlin-android')

buildscript {
//    ext.kotlin_version = '1.3.61'
    ext.kotlin_version = '1.5.30'
    repositories {
        google()
        jcenter()
    }
    dependencies {
//        classpath 'com.android.tools.build:gradle:3.6.1'
        classpath 'com.android.tools.build:gradle:4.0.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

 

Check detailed log here:

> Task :app:kaptDebugKotlin

w: Runtime JAR files in the classpath should have the same version. These files were found in the classpath:

    /Users/pradiptilala/.gradle/caches/transforms-2/files-2.1/a6ee74fb178b3ea5a892efce09fbf14f/jetified-kotlin-stdlib-jdk7-1.3.61.jar (version 1.3)

    /Users/pradiptilala/.gradle/caches/transforms-2/files-2.1/120c1f3b8c4010532adc0c31034c2e5b/jetified-kotlin-stdlib-1.4.0.jar (version 1.4)

    /Users/pradiptilala/.gradle/caches/transforms-2/files-2.1/ce684c39ae6924170520b6f45161379f/jetified-kotlin-stdlib-common-1.4.0.jar (version 1.4)

w: Some runtime JAR files in the classpath have an incompatible version. Consider removing them from the classpath

> Task :app:kaptDebugKotlin FAILED

java.lang.IllegalStateException: failed to analyze: java.lang.reflect.InvocationTargetException

at org.jetbrains.kotlin.analyzer.AnalysisResult.throwIfError(AnalysisResult.kt:56)

at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:182)

at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:165)

at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:55)

at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:84)

at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:42)

at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:104)

at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1558)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:566)

at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)

at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)

at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)

at java.base/java.security.AccessController.doPrivileged(Native Method)

at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)

at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)

at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)

at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)

at java.base/java.security.AccessController.doPrivileged(Native Method)

at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)

at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)

at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)

at java.base/java.lang.Thread.run(Thread.java:834)

Caused by: java.lang.reflect.InvocationTargetException

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:566)

at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing(annotationProcessing.kt:76)

at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing$default(annotationProcessing.kt:35)

at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.runAnnotationProcessing(Kapt3Extension.kt:224)

at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:187)

at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:98)

at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM$analyzeFilesWithJavaIntegration$2.invoke(TopDownAnalyzerFacadeForJVM.kt:97)

at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:107)

at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:82)

at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:557)

at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:82)

at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:107)

at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:548)

at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:177)

... 23 more

Caused by: com.sun.tools.javac.processing.AnnotationProcessingError: java.lang.ExceptionInInitializerError

at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:992)

at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:896)

at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1222)

at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1335)

at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1258)

at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1157)

... 40 more

Caused by: java.lang.ExceptionInInitializerError

at androidx.room.processor.DatabaseProcessor.doProcess(DatabaseProcessor.kt:82)

at androidx.room.processor.DatabaseProcessor.process(DatabaseProcessor.kt:57)

at androidx.room.RoomProcessor$DatabaseProcessingStep.process(RoomProcessor.kt:134)

at com.google.auto.common.BasicAnnotationProcessor.process(BasicAnnotationProcessor.java:330)

at com.google.auto.common.BasicAnnotationProcessor.process(BasicAnnotationProcessor.java:181)

at org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor.process(incrementalProcessors.kt)

at org.jetbrains.kotlin.kapt3.base.ProcessorWrapper.process(annotationProcessing.kt:147)

at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:980)

... 45 more

Caused by: java.lang.Exception: No native library is found for os.name=Mac and os.arch=aarch64. path=/org/sqlite/native/Mac/aarch64

at org.sqlite.SQLiteJDBCLoader.loadSQLiteNativeLibrary(SQLiteJDBCLoader.java:333)

at org.sqlite.SQLiteJDBCLoader.initialize(SQLiteJDBCLoader.java:64)

at androidx.room.verifier.DatabaseVerifier.<clinit>(DatabaseVerifier.kt:68)

... 53 more

Execution failed for task ':app:kaptDebugKotlin'.

> Internal compiler error. See log for more details


Parcelize annotations from package 'kotlinx.android.parcel' are deprecated. Change package to 'kotlinx.parcelize'

Parcelize annotations from package 'kotlinx.android.parcel' are deprecated. Change package to 'kotlinx.parcelize'

I was using import statement like below when I was getting this warning
import kotlinx.android.parcel.Parcelize
Finally, replace old import with new import
import kotlinx.parcelize.Parcelize
Update to latest kotlin version - 1.4.20 and use below plugin

apply plugin: 'kotlin-parcelize'

java.lang.IllegalArgumentException: Receiver not registered : Android BroadcastReceiver

     --------- beginning of crash

2021-06-29 14:58:18.780 16932-16932/com.example.myapplication2 E/AndroidRuntime: FATAL EXCEPTION: main

    Process: com.example.myapplication2, PID: 16932

    java.lang.IllegalArgumentException: Receiver not registered: com.example.myapplication2.Activities.Affirmation.ShowAffirmationActivity$broadcastReceiver$1@94b6d81

        at android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:1433)

        at android.app.ContextImpl.unregisterReceiver(ContextImpl.java:1553)

        at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:674)

        at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:674)

        at com.example.myapplication2.Activities.Affirmation.ShowAffirmationActivity.unregisterReciever(ShowAffirmationActivity.kt:90)

        at com.example.myapplication2.Activities.Affirmation.ShowAffirmationActivity$broadcastReceiver$1.onReceive(ShowAffirmationActivity.kt:36)

        at androidx.localbroadcastmanager.content.LocalBroadcastManager.executePendingBroadcasts(LocalBroadcastManager.java:313)

        at androidx.localbroadcastmanager.content.LocalBroadcastManager$1.handleMessage(LocalBroadcastManager.java:121)

        at android.os.Handler.dispatchMessage(Handler.java:107)

        at android.os.Looper.loop(Looper.java:224)

        at android.app.ActivityThread.main(ActivityThread.java:7562)

        at java.lang.reflect.Method.invoke(Native Method)

        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)

        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

2021-06-29 14:58:18.784 16932-16932/com.example.myapplication2 E/MQSEventManagerDelegate: failed to get MQSService.

2021-06-29 14:58:18.786 16932-16932/com.example.myapplication2 E/CustomActivityOnCrash: App has crashed, executing CustomActivityOnCrash's UncaughtExceptionHandler

    java.lang.IllegalArgumentException: Receiver not registered: com.example.myapplication2.Activities.Affirmation.ShowAffirmationActivity$broadcastReceiver$1@94b6d81

        at android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:1433)

        at android.app.ContextImpl.unregisterReceiver(ContextImpl.java:1553)

        at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:674)

        at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:674)

        at com.example.myapplication2.Activities.Affirmation.ShowAffirmationActivity.unregisterReciever(ShowAffirmationActivity.kt:90)

        at com.example.myapplication2.Activities.Affirmation.ShowAffirmationActivity$broadcastReceiver$1.onReceive(ShowAffirmationActivity.kt:36)

        at androidx.localbroadcastmanager.content.LocalBroadcastManager.executePendingBroadcasts(LocalBroadcastManager.java:313)

        at androidx.localbroadcastmanager.content.LocalBroadcastManager$1.handleMessage(LocalBroadcastManager.java:121)

        at android.os.Handler.dispatchMessage(Handler.java:107)

        at android.os.Looper.loop(Looper.java:224)

        at android.app.ActivityThread.main(ActivityThread.java:7562)

        at java.lang.reflect.Method.invoke(Native Method)

        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)

        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)


Explanation:

I am using directly unregisterReceiver(broadcastReceiver).

It's throwing error java.lang.IllegalArgumentException: Receiver not registered. You can check error in detail above.


Solution:

Use below to unregisterReceiver your local broadcast receiver.

LocalBroadcastManager.getInstance(this@ShowAffirmationActivity).unregisterReceiver(broadcastReceiver)

I have registered broad cast receiver in onCreate using below code.
LocalBroadcastManager.getInstance(this@ShowAffirmationActivity).registerReceiver(broadcastReceiver, IntentFilter(ACTION_FINISH_ALARM_ACTIVITY))

I have declared broad cast receiver at start of activity as shown below. This code only receives broad cast once and unregister broadcast receiver before finishing activity. This works perfectly fine. 
private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("ACTION_FINISH_ALARM::", "Status::"+intent.action)
if (ACTION_FINISH_ALARM_ACTIVITY.equals(intent.action)) // 1 == lock
{
unregisterReceiver()
alarmOptions?.stopVibrationAndRinging()
finishAndRemoveTask()
}
}
}

java.lang.IllegalStateException at android.media.MediaPlayer : Android MediaPlayer Error

     --------- beginning of crash

2021-06-29 12:40:18.925 2968-2968/com.example.myapplication2 E/AndroidRuntime: FATAL EXCEPTION: main

    Process: com.example.myapplication2, PID: 2968

    java.lang.RuntimeException: Unable to stop service com.example.myapplication2.Util.AffirmationRingtoneService@4ce3875: java.lang.IllegalStateException

        at android.app.ActivityThread.handleStopService(ActivityThread.java:4201)

        at android.app.ActivityThread.access$2000(ActivityThread.java:224)

        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1924)

        at android.os.Handler.dispatchMessage(Handler.java:107)

        at android.os.Looper.loop(Looper.java:224)

        at android.app.ActivityThread.main(ActivityThread.java:7562)

        at java.lang.reflect.Method.invoke(Native Method)

        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)

        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

     Caused by: java.lang.IllegalStateException

        at android.media.MediaPlayer._stop(Native Method)

        at android.media.MediaPlayer.stop(MediaPlayer.java:1418)

        at com.example.myapplication2.Util.AffirmationRingtoneService.stopMediaPlayerAfterAlarmFire(AffirmationRingtoneService.kt:72)

        at com.example.myapplication2.Util.AffirmationRingtoneService.onDestroy(AffirmationRingtoneService.kt:81)

        at android.app.ActivityThread.handleStopService(ActivityThread.java:4177)

        at android.app.ActivityThread.access$2000(ActivityThread.java:224) 

        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1924) 

        at android.os.Handler.dispatchMessage(Handler.java:107) 

        at android.os.Looper.loop(Looper.java:224) 

        at android.app.ActivityThread.main(ActivityThread.java:7562) 

        at java.lang.reflect.Method.invoke(Native Method) 

        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) 

        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950) 

2021-06-29 12:40:18.931 2968-2968/com.example.myapplication2 E/MQSEventManagerDelegate: failed to get MQSService.

2021-06-29 12:40:18.932 2968-2968/com.example.myapplication2 E/CustomActivityOnCrash: App has crashed, executing CustomActivityOnCrash's UncaughtExceptionHandler

    java.lang.RuntimeException: Unable to stop service com.example.myapplication2.Util.AffirmationRingtoneService@4ce3875: java.lang.IllegalStateException

        at android.app.ActivityThread.handleStopService(ActivityThread.java:4201)

        at android.app.ActivityThread.access$2000(ActivityThread.java:224)

        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1924)

        at android.os.Handler.dispatchMessage(Handler.java:107)

        at android.os.Looper.loop(Looper.java:224)

        at android.app.ActivityThread.main(ActivityThread.java:7562)

        at java.lang.reflect.Method.invoke(Native Method)

        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)

        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

     Caused by: java.lang.IllegalStateException

        at android.media.MediaPlayer._stop(Native Method)

        at android.media.MediaPlayer.stop(MediaPlayer.java:1418)

        at com.example.myapplication2.Util.AffirmationRingtoneService.stopMediaPlayerAfterAlarmFire(AffirmationRingtoneService.kt:72)

        at com.example.myapplication2.Util.AffirmationRingtoneService.onDestroy(AffirmationRingtoneService.kt:81)

        at android.app.ActivityThread.handleStopService(ActivityThread.java:4177)

        at android.app.ActivityThread.access$2000(ActivityThread.java:224) 

        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1924) 

        at android.os.Handler.dispatchMessage(Handler.java:107) 

        at android.os.Looper.loop(Looper.java:224) 

        at android.app.ActivityThread.main(ActivityThread.java:7562) 

        at java.lang.reflect.Method.invoke(Native Method) 

        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) 

        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950) 

Google Keep stopping - com.google.android.apps.gsa.tasks.m: EXCLUSIVE background task UPDATE_HOTWORD_MODELS crashed.


 com.google.android.apps.gsa.tasks.m: EXCLUSIVE background task UPDATE_HOTWORD_MODELS crashed.

at com.google.android.apps.gsa.tasks.n.ga(SourceFile:7)

at com.google.android.libraries.gsa.k.a.n.ga(SourceFile:2)

at com.google.common.w.a.bv.run(SourceFile:6)

at com.google.android.apps.gsa.shared.util.c.a.bt.b(SourceFile:1)

at com.google.android.apps.gsa.shared.util.c.a.bw.run(SourceFile:1)

at com.google.android.libraries.g.aa.run(SourceFile:1)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)

at com.google.android.libraries.g.m.run(Unknown Source:2)

at com.google.android.libraries.g.e.run(Unknown Source:2)

at java.lang.Thread.run(Thread.java:919)

Suppressed: com.google.android.apps.gsa.shared.util.c.a.bv: Unchecked exception happened while running task: n[UPDATE_HOTWORD_MODELS-failure-handler]

at com.google.android.apps.gsa.shared.util.c.a.bw.run(SourceFile:6)

... 6 more

Caused by: com.google.android.apps.gsa.tasks.k: java.lang.IllegalArgumentException: Multiple entries with same key: en-AE=https://www.gstatic.com/android-search/hotword/x_google/9b74550b2fb24680fdac9ec59c56e828/hotword.data and en-AE=https://www.gstatic.com/android-search/hotword/x_google/9b74550b2fb24680fdac9ec59c56e828/hotword.data

at com.google.android.apps.gsa.tasks.b.c.b(SourceFile:2)

at com.google.android.apps.gsa.tasks.b.a.a(Unknown Source:4)

at com.google.android.libraries.gsa.k.a.g.a(Unknown Source:2)

at com.google.common.w.a.dr.a(SourceFile:1)

at com.google.common.w.a.co.run(SourceFile:4)

at com.google.common.w.a.dt.run(SourceFile:1)

... 6 more

Caused by: java.lang.IllegalArgumentException: Multiple entries with same key: en-AE=https://www.gstatic.com/android-search/hotword/x_google/9b74550b2fb24680fdac9ec59c56e828/hotword.data and en-AE=https://www.gstatic.com/android-search/hotword/x_google/9b74550b2fb24680fdac9ec59c56e828/hotword.data

at com.google.common.b.ps.v(SourceFile:1)

at com.google.common.b.ps.g(SourceFile:20)

at com.google.common.b.ps.e(SourceFile:6)

at com.google.common.b.gx.b(SourceFile:1)

at com.google.android.apps.gsa.shared.speech.hotword.c.a(SourceFile:9)

at com.google.android.apps.gsa.speech.hotword.b.a.a(SourceFile:8)

at com.google.android.apps.gsa.speech.hotword.b.a.b(SourceFile:3)

at com.google.android.apps.gsa.staticplugins.microdetection.b.p.e(SourceFile:1)

at com.google.android.apps.gsa.staticplugins.microdetection.b.p.b(SourceFile:2)

at com.google.android.apps.gsa.staticplugins.microdetection.b.p.a(SourceFile:28)

at com.google.android.apps.gsa.tasks.b.c.b(SourceFile:1)

... 11 more


java.lang.ClassCastException: java.util.Collections$SingletonList cannot be cast to java.util.ArrayList

Issue detail:
I am parsing the List using gson in kotlin with below syntax:
val methods: ArrayList<DataPOJO> = Gson().fromJson(dataObj.getString(withdraw_methods),
Array<DataPOJO>::class.java).toList() as ArrayList<DataPOJO>
Here, It's working fine while API response "dataObj.getString(withdraw_methods)" string array contains more than one object.
In case of one object its giving below error: java.lang.ClassCastException: java.util.Collections$SingletonList cannot be cast to java.util.ArrayList

Solution:
val methods: ArrayList<DataPOJO> = ArrayList(Gson().fromJson(dataObj.getString(withdraw_methods), Array<DataPOJO>::class.java).toList())

Best article to understand SingletonList Vs. List::of -- Collections :: Singleton List Showdown

Detailed error log Android Studio:
02-20 13:35:20.370 17010-17010/com.myandriodapp W/System.err: java.lang.ClassCastException: java.util.Collections$SingletonList cannot be cast to java.util.ArrayList
02-20 13:35:20.372 1483-1507/? I/Timeline: Timeline: Activity_windows_visible id: ActivityRecord{c8e1655 u0 com.myandriodapp/.v3_kotlin.cashback.user_activity.ProfileActivity t262} time:51048610
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at com.myandriodapp.v3_kotlin.cashback.user_activity.WithdrawalFragment$onActivityCreated$5.onChanged(WithdrawalFragment.kt:199)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at com.myandriodapp.v3_kotlin.cashback.user_activity.WithdrawalFragment$onActivityCreated$5.onChanged(WithdrawalFragment.kt:50)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at com.myandriodapp.v3_kotlin.cashback.user_activity.viewmodel.WithdrawalViewModel$AppWithdrawMethodsAPI$1.invokeSuspend(WithdrawalViewModel.kt:101)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at android.os.Handler.handleCallback(Handler.java:742)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:95)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at android.os.Looper.loop(Looper.java:157)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5603)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:774)
02-20 13:35:20.395 17010-17010/com.myandriodapp W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:652)

custom spinner adapter android kotlin

Spinner Custom Item Layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageViewFlag"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_margin="5dp"
        app:layout_constraintEnd_toStartOf="@+id/textViewCountryName"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <TextView
        android:id="@+id/textViewCountryName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Demo"
        android:textColor="#000"
        android:padding="7dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/imageViewFlag"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Spinner Custom Adapter
package com.example.customspinnerexamplekotlin

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import kotlinx.android.synthetic.main.item_custom_spinner.view.*


class CustomSpinnerAdapter(ctx: Context, countries: ArrayList<CountryData>) : ArrayAdapter<CountryData>(ctx, 0, countries) {

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        return createItemView(position, convertView, parent);
    }

    override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
        return createItemView(position, convertView, parent);
    }

    fun createItemView(position: Int, recycledView: View?, parent: ViewGroup):View {
        val country = getItem(position)

        val view = recycledView ?: LayoutInflater.from(context).inflate(
                R.layout.item_custom_spinner,
                parent,
                false
        )

        country?.let {
            view.imageViewFlag.setImageResource(country.flag)
            view.textViewCountryName.text = country.countryName
        }
        return view
    }
}

CountryData Object
package com.example.customspinnerexamplekotlin

class CountryData (val countryName: String,
                   val flag: Int)

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f3f3f3">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="16dp" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineEnd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_end="16dp" />

    <com.google.android.material.textview.MaterialTextView
        android:id="@+id/tv_selected_item"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:hint="Select Country from below spinner"
        android:textColorHint="#000000"
        app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
        app:layout_constraintStart_toStartOf="@id/guidelineStart"
        app:layout_constraintTop_toTopOf="parent">
    </com.google.android.material.textview.MaterialTextView>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/cl_spinner"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="#ffffff"
        android:padding="5dp"
        app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
        app:layout_constraintStart_toStartOf="@+id/guidelineStart"
        app:layout_constraintTop_toBottomOf="@id/tv_selected_item">

        <androidx.appcompat.widget.AppCompatSpinner
            android:id="@+id/spinner"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt
package com.example.customspinnerexamplekotlin

import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setCustomAdapterSpinner()
    }

    fun setCustomAdapterSpinner() {

        val country_list = arrayListOf<CountryData>()

        country_list.add(CountryData("India", R.drawable.ic_flag_black_24dp))
        country_list.add(CountryData("United States", R.drawable.ic_flag_black_24dp))
        country_list.add(CountryData("Indonesia", R.drawable.ic_flag_black_24dp))
        country_list.add(CountryData("France", R.drawable.ic_flag_black_24dp))
        country_list.add(CountryData("China", R.drawable.ic_flag_black_24dp))

        val adapter = CustomSpinnerAdapter(
                this,
                country_list
        )

        spinner.adapter = adapter

        spinner.setOnItemSelectedListener(object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>?, p1: View?, pos: Int, p3: Long) {
                Toast.makeText(this@MainActivity, "" + (parent?.getItemAtPosition(pos) as CountryData).countryName, Toast.LENGTH_SHORT).show()
            }

            override fun onNothingSelected(p0: AdapterView<*>?) {
            }
        })

        // dynamically adding data after setting adapter to spinner

        country_list.add(CountryData("Japan", R.drawable.ic_flag_black_24dp))
        country_list.add(CountryData("New Zealand", R.drawable.ic_flag_black_24dp))
        country_list.add(CountryData("Other", R.drawable.ic_flag_black_24dp))

        adapter.notifyDataSetChanged()
    }

}

android kotlin custom spinner adapter
Android kotlin Custom Spinner Adapter example


custom spinner with image and text android kotlin
Custom spinner Adapter with image and text android kotlin



android spinner example kotlin

android spinner example - string array xml

res/values/strings.xml
<resources>
    <string name="app_name">KotlinSpinnerExample</string>

    <string-array name="country_arrays">
        <item>India</item>
        <item>United States</item>
        <item>Indonesia</item>
        <item>France</item>
        <item>China</item>
        <item>Japan</item>
        <item>New Zealand</item>
        <item>Other</item>
    </string-array>

</resources>

Populate the Spinner with string array choices

With an array such as mentioned above, you can use below code in your Activity or Fragment to set spinner with the string array using an ArrayAdapter instance:

package com.example.kotlinspinnerexample

import androidx.appcompat.app.AppCompatActivity

import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setSimpleSpinner()
    }

    fun setSimpleSpinner() {
        // Create an ArrayAdapter using the string array (country_arrays) and a default spinner layout
        ArrayAdapter.createFromResource(
                this,
                R.array.country_arrays,
                android.R.layout.simple_spinner_item
        ).also { adapter ->
            // Specify the layout to use when the list of choices appears
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
            // Apply the adapter to the spinner
            spinner.adapter = adapter
        }

       spinner.setOnItemSelectedListener(object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>?, p1: View?, pos: Int, p3: Long) {
                Toast.makeText(this@MainActivity, "" + parent?.getItemAtPosition(pos).toString(), Toast.LENGTH_SHORT).show()
            }
            override fun onNothingSelected(p0: AdapterView<*>?) {
            }
        })
    }
}


Here, I have put the spinner inside the ConstraintLayout and guildeline is on both sides. ConstraintLayout  has white background  and wraps AppCompatSpinner. So I can highlight the spinner according to background (background is grey here).
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:background="#f3f3f3">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="16dp" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineEnd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_end="16dp" />

    <com.google.android.material.textview.MaterialTextView
        android:id="@+id/tv_selected_item"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:hint="Select Country from below spinner"
        android:textColorHint="#000000"
        app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
        app:layout_constraintStart_toStartOf="@id/guidelineStart"
        app:layout_constraintTop_toTopOf="parent">
    </com.google.android.material.textview.MaterialTextView>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/cl_spinner"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="#ffffff"
        android:padding="5dp"
        app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
        app:layout_constraintStart_toStartOf="@+id/guidelineStart"
        app:layout_constraintTop_toBottomOf="@id/tv_selected_item">

        <androidx.appcompat.widget.AppCompatSpinner
            android:id="@+id/spinner"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
android kotlin spinner adapter
android kotlin - Spinner example

set string array to spinner android xml
Populate the Spinner with country string array

how to set value in spinner dynamically in android

var country_list = arrayListOf<String>()

        country_list.add("India")
        country_list.add("United States")
        country_list.add("Indonesia")
        country_list.add("France")
        country_list.add("China")
        country_list.add("Japan")
        country_list.add("New Zealand")
        country_list.add("Other")

val adapter = ArrayAdapter(
                this, // Context
                android.R.layout.simple_spinner_dropdown_item, // Layout
                country_list // ArrayList
        )

spinner.adapter = adapter
        

How to use Kotlin Coroutines with volley

How to use API connection with Kotlin Coroutines + Volley
When start developing API with Volley in Kotlin, you want to use Kotlin Coroutine instead of AsyncTask. We see how to use API connection using Volley and Coroutine as an example.
First of all, How can I take advantage of coroutines so I can write my code in concise manner:
Here's an example to Coroutine calling API Android

Volley overview

Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster. Volley is available on GitHub. Volley is an HTTP client developed by google for android development. You can use suspendCancellableCoroutine to make the Volley API request.

NetworkUtility.kt
package com.example.myapplication

import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.Volley
import kotlinx.coroutines.suspendCancellableCoroutine
import org.json.JSONObject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

class NetworkUtility {
 
    companion object {
        val mRequestQueue: RequestQueue by lazy {
            Volley.newRequestQueue(MainApp.get())
        }

        suspend fun APIrequest(url: String): JSONObject {

            return suspendCancellableCoroutine { continuation ->
                try {
                    // Sucess Listner
                    val success = Response.Listener<JSONObject> { response ->
                        if (continuation.isActive) {
                            continuation.resume(response)
                        }
                    }

                    // Error Listner
                    val error = Response.ErrorListener { error ->
                        if (continuation.isActive) {
                            continuation.resume(JSONObject())
                        }
                    }

                    val jsonObjectRequest =
                        JsonObjectRequest(Request.Method.GET, url, null, success, error)

                    mRequestQueue.add(jsonObjectRequest)

                } catch (e: Exception) {
                    e.printStackTrace()
                    if (continuation.isActive) {
                        if (continuation.isActive) {
                            continuation.resumeWithException(e)
                        }
                    }
                }
            }
        }
    }
 
}

MainViewModel.kt
package com.example.myapplication

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject

class MainViewModel : ViewModel() {

    var usersList: MutableLiveData<ArrayList<User>> = MutableLiveData(arrayListOf())

    var isLoadingData = true
    var showProgress = MutableLiveData<Boolean>()


    fun getData(urlStr: String) {
        showProgress.value = true

        viewModelScope.launch(Dispatchers.IO) {
            val rss = NetworkUtility.APIrequest(urlStr)

            withContext(Dispatchers.Main) {
                // call to UI thread
                isLoadingData = false
                showProgress.value = false
                usersList.value?.addAll(parseJsonString(rss.toString()))
            }
        }
    }

    fun parseJsonString(str: String): ArrayList<User> {
        val jsonOb = JSONObject(str)
        val list: ArrayList<User> = arrayListOf()
        val jsonArray = jsonOb.getJSONArray("data");

        for (j in 0..jsonArray.length() - 1) {
            val user = User(
                jsonArray.getJSONObject(j).getString("id"),
                jsonArray.getJSONObject(j).getString("email"),
                jsonArray.getJSONObject(j).getString("first_name"),
                jsonArray.getJSONObject(j).getString("last_name"),
                jsonArray.getJSONObject(j).getString("avatar")
            )
            list.add(user)
        }
        return list
    }

}


MainActivity.kt
package com.example.myapplication

import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.myapplication.databinding.ActivityMainBinding
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.toolbar.*

class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel
    lateinit var userAdapter: UserRVAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)

        setSupportActionBar(toolbar)
        getSupportActionBar()?.setDisplayHomeAsUpEnabled(true)

        viewModel = ViewModelProviders.of(this)[MainViewModel::class.java]
        binding.mainVM = viewModel
        binding.setLifecycleOwner(this)

        viewModel.getData("https://reqres.in/api/users?page=1&per_page=12")

        viewModel.showProgress.observe(this, Observer {
            if (it == true) {
                progressBar.visibility = View.VISIBLE
            } else {
                progressBar.visibility = View.GONE
            }
        })

        viewModel.usersList.observe(this, Observer {
            Log.e("ResSize", "" + it.size + "::" + it)
            userAdapter.addList(it)
        })

        // Creates a vertical Layout Manager
        rv_users.layoutManager = LinearLayoutManager(this)
        userAdapter = UserRVAdapter(this)
        rv_users.adapter = userAdapter
    }
}

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable name="mainVM" type="com.example.myapplication.MainViewModel" />

    </data>
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    tools:context=".MainActivity">

    <include layout="@layout/toolbar" />

    <TextView
        app:layout_constraintTop_toBottomOf="@+id/toolbar"
        android:id="@+id/tv_select_user"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Users"
        android:layout_margin="7dp"
        android:textColor="@android:color/black"
        app:layout_constraintLeft_toLeftOf="parent"
        android:textSize="20sp"/>


    <androidx.recyclerview.widget.RecyclerView
        android:layout_marginTop="10dp"
        app:layout_constraintTop_toBottomOf="@id/tv_select_user"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:id="@+id/rv_users"
        android:layout_width="match_parent"
        android:layout_height="0dp"/>

    <ProgressBar
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:id="@+id/progressBar"
        android:visibility="gone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

UserRVAdapter.kt
package com.example.myapplication

import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import kotlinx.android.synthetic.main.ly_item_user.view.*

class UserRVAdapter(val context: Context): RecyclerView.Adapter<UserRVAdapter.ViewHolder>() {

    var items : ArrayList<User> = arrayListOf()

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val posObj=items.get(position);

        holder.tv_name1?.text =posObj.first_name +" "+posObj.last_name
        holder.tv_email1?.text = posObj.email

        GlideApp.with(holder.iv_profile1.context)
            .load(posObj.avatar)
            .diskCacheStrategy(DiskCacheStrategy.ALL)
            .apply(RequestOptions.circleCropTransform())
            .error(R.drawable.ic_launcher_background)
            .placeholder(R.drawable.ic_launcher_background)
            .into(holder.iv_profile1)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(context).inflate(R.layout.ly_item_user, parent, false))
    }

    fun addList(itemsNew : ArrayList<User>){
            this.items=itemsNew
            notifyDataSetChanged()
            Log.e("ResSize:ItemsIF", "" + items.size)
    }

    override fun getItemCount(): Int {
        return items.size
    }

    class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
        // Holds the TextView that will add each animal to
        val tv_name1 = view.tv_name
        val tv_email1 = view.tv_email
        val iv_profile1 = view.iv_profile
    }
}

ly_item_user.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/iv_profile"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_width="50dp"
        android:layout_margin="10dp"
        android:layout_height="50dp"/>

    <TextView
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toRightOf="@id/iv_profile"
        app:layout_constraintBottom_toTopOf="@+id/tv_email"
        android:id="@+id/tv_name"
        android:textSize="20sp"
        android:text="Pradip"
        android:layout_marginLeft="10dp"
        android:textColor="@android:color/black"
        app:layout_constraintVertical_chainStyle="packed"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:layout_marginLeft="10dp"
        app:layout_constraintLeft_toRightOf="@id/iv_profile"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_name"
        android:id="@+id/tv_email"
        android:text="Tilala"
        android:textColor="@android:color/darker_gray"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <View
        app:layout_constraintTop_toBottomOf="@id/iv_profile"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_width="match_parent"
        android:background="#f3f3f3"
        android:layout_margin="10dp"
        android:layout_height="2dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Best reference : API connection with Volley + Coroutine

Popular Posts