Skip to content

Instantly share code, notes, and snippets.

@roxxydev
Last active November 26, 2020 16:06
Show Gist options
  • Select an option

  • Save roxxydev/4ac2f2e4cddfbd1583703d5b392df603 to your computer and use it in GitHub Desktop.

Select an option

Save roxxydev/4ac2f2e4cddfbd1583703d5b392df603 to your computer and use it in GitHub Desktop.
Android with Kotlin
  • kotlin_lambda.md - Lambda in Kotlin
  • HiltExampleActivity.kt - Basic usage of Hilt dependency injection

Pointers about handling exception in coroutines

  • CoroutineExceptionHandler is invoked only on uncaught exceptions — exceptions that were not handled in any other way. In particular, all children coroutines (coroutines created in the context of another Job) delegate handling of their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root, so the CoroutineExceptionHandler installed in their context is never used. In addition to that, async builder always catches all exceptions and represents them in the resulting Deferred object, so its CoroutineExceptionHandler has no effect either.
  • Coroutines internally use CancellationException for cancellation, these exceptions are ignored by all handlers, so they should be used only as the source of additional debug information, which can be obtained by catch block. When a coroutine is cancelled using Job.cancel, it terminates, but it does not cancel its parent.
  • Throwing exception child coroutines will cause an error and app to crash.
  • If a coroutine encounters an exception other than CancellationException, it cancels its parent with that exception.
  • The original exception is handled by the parent (in CoroutineExceptionHandler) only when all its children terminate, which is demonstrated by the following example.
  • When multiple children of a coroutine fail with an exception, the general rule is "the first exception wins", so the first exception gets handled. All additional exceptions that happen after the first one are attached to the first exception as suppressed ones.
  • crucial difference between regular and supervisor jobs is exception handling. Every child should handle its exceptions by itself via the exception handling mechanism. This difference comes from the fact that child's failure does not propagate to the parent. It means that coroutines launched directly inside the supervisorScope do use the CoroutineExceptionHandler that is installed in their scope in the same way as root coroutines do.

Example:

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    supervisorScope {
        val child = launch(handler) {
            println("The child throws an exception")
            throw AssertionError()
        }
        println("The scope is completing")
    }
    println("The scope is completed")
}

Output:

The scope is completing
The child throws an exception
CoroutineExceptionHandler got java.lang.AssertionError
The scope is completed
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class HiltExampleActivity : AppCompatActivity() {
// Using Field injection
@Inject
lateinit var someClass: SomeClass
@Inject
lateinit var anotherClass: AnotherClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println(someClass.doAThing())
println(anotherClass.doAThing())
println(anotherClass.doAThing2())
}
}
// Using Constructor injection
class SomeClass
@Inject
constructor(
private val someDependency: SomeDependency
) {
fun doAThing(): String{
return "Look I got: ${someDependency.getAThing()}"
}
}
class SomeDependency
@Inject
constructor() {
fun getAThing() : String{
return "A Thing"
}
}
// With Interface
class AnotherClass
@Inject
constructor(
private val someInterfaceImpl1: SomeInterface,
@Impl2 private val someInterfaceImpl2: SomeInterface
) {
fun doAThing(): String {
return "Look I got: ${someInterfaceImpl1.getAThing()}"
}
fun doAThing2(): String {
return "Look I got: ${someInterfaceImpl2.getAThing()}"
}
}
interface SomeInterface {
fun getAThing(): String
}
class SomeInterfaceImpl1
@Inject
constructor(): SomeInterface {
override fun getAThing() : String {
return "A Thing1"
}
}
class SomeInterfaceImpl2
@Inject
constructor(): SomeInterface {
override fun getAThing() : String {
return "A Thing2"
}
}
@InstallIn(ApplicationComponent::class)
@Module
class MyModule {
@Singleton
@Provides
fun provideSomeInterface1(): SomeInterface {
return SomeInterfaceImpl1()
}
@Impl2
@Singleton
@Provides
fun provideSomeInterface2(): SomeInterface {
return SomeInterfaceImpl2()
}
/*
// Use Provides instead of Binds to solve issue for injecting libraries interfaces you don't own
@Singleton
@Provides
fun provideGson(): Gson {
return Gson()
}
*/
}
// Providing instances of the same type (SomeInterface)
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Impl2

Lambda as an argument

  • You can put the whole lambda inside the parentheses.
list.any({ i: Int -> i > 0 })
  • You can move lambda out of parentheses if the lambda is the last argument.
list.any() { i : Int -> i > 0}
  • If the parentheses are empty you can even omit them.
list.any { i: Int -> i > 0 }
  • If lambda has the only argument, you can replace its name with ‘it’.
list.any{ it > 0 }

Storing Lambda in a variable

  • A variable whose type is a function that takes two integer parameters and returns integer as a result
val sum = { x: Int, y: Int -> x=y }
  • A variable whose type is a function that takes Int as a parameter,and returns Boolean as result
val isEven = { i: Int -> i % 2 ==0 }

Using lambda declared as a class extensions

val p : String.(Int) -> String = { this + it }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment