- Arrays
- Sealed class and interfaces
- Mutable and immutable classes
- Strings
- Val , var , const , lambda , high order function.
- Coroutine and their working
- flow and operators
- In kotlin we call kotlin functions as first class citizen , like we can store function in variable , we cam pass lambda to function , pass function as parameter.
- take function as input or return function as output
- A function that does not have a name called lambda
val sum = {x:Int,y:Int -> x+y}
fun main() {
calculate(2, 3, ::sum)
}
fun sum(a: Int, b: Int): Int {
return a + b
}
fun calculate(a: Int, b: Int, add: (Int, Int) -> Int) {
val sum = add(a, b)
println(sum)
}- We do not need to extends the object to extends is current functionality in kotlin.
val person = Person().myFunction {
name = "My Name"
sirName = "My Sir Name"
}
class Person {
var name: String = ""
var sirName: String = ""
}
//extension function
fun <T> T.myFunction(block: T.() -> Unit): T {
block()
return this
}
- Scope functions are high order function in kotlin.
- Scope function that return the lambda
- let , run , with
- Scope function that return the object it self
- also , apply
- Scope function that object reference to
this- with , run and apply.
- Scope function that object reference to
it- let , also.
- let
- it will use to check the nullability of calling object.
- context of object refer by it.
- return the lamda, last like of block.
- also
- Similar to let , context object refer by it.
- return the same instance of object.
- its like we do and also do it.
- run
- Similar to apply,
- contest object refer to this.
- return the lambda , last line of block.
- apply
- its like apply the modification on object, it is used to modify the property of object.
- object context refer by this
- return the object itself
- with
- similar to run,
- context object refer with this.
- return lambda
- Example
var name: String? = "Initial"
val student = Student()
val length: Int = name?.let {
// return lambda
val newValue = it.plus("Name")
newValue.length
} ?: 0
println("length with null safety and with let $length")
val studentRun: String = Student().run {
// return lambda
name = "Name Run"
age = 151
"$name and $age WithRun"
}
println("studentRun $studentRun")
val inWords: String = with(Student()) {
name = "With Name"
age = 10
// return lambda
"name $name and Age $age"
}
val studentAfterAlso: Student = student.also {
// return same object
"Name of $it" // no use
it.age = 10
}
println("StudentAfterAlso $studentAfterAlso")
val studentApply: Student = Student().apply {
// return Same object
name = "Apply Name"
age = 15
}
- Backing Field
- Backing field in kotlin represent by
fieldkeywords, it will use to store the value of its own property. - We can use
fieldbacking field only with getter and setter in kotlin. - if we use the property name within the property getter and setter it will
throw
StackOverFlowExceptionbecause it will call itself in recursion. - Backing field is generated when the are call with in the getter and setter or that its own property.
- Example: this will not create the backing field because it is using someother property not included field in this getter.
val isPass: Boolean get() = marks > 30 var name:String = "" // No backing field generated.
- Example:- in this example we are using
fieldkeywords so the backing field is generated.
var name: String = "NO Name" get() { println("Name Student => $field") return "Complete Name => $field" } set(value) { field = if (marks > 80) "Good Student $value" else value }
- Backing field in kotlin represent by
- Backing Properties
- This one is like we will provide the immutable property to the outside so the dataset change will be done by its own class only.
- Example -> do the things in viewModel when we expose the data by flow or live data.
class Student {
private var _name: String = ""
val name: String get() = _name
// We can do the changes if we do this.
var courseName: String = ""
get() = _courseName // backing property
//after this we will get the same value not updated because
// in get() we are returning the _courseName.
}
fun main() {
val student = Student()
student.name
student.name = "hello" // its can not be compile, as it is val.
}
Sealed classes and interfacesrepresent restricted class hierarchies that provide more control over inheritance. All direct subclasses of a sealed class are known at compile time.- For example, third-party clients can't extend your sealed class in their code.
- The same works for
sealed interfacesand their implementations: once a module with a sealed interface is compiled, no new implementations can appear. - sealed classes are similar to enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances, each with its own state
- Direct subclasses of sealed classes and interfaces must be declared in the same package.
- Example:-
sealed interface Error
sealed class IOError() : Error
class FileReadError(val f: File) : IOError()
class DatabaseError(val source: DataSource) : IOError()
object RuntimeError : Error- Sealed classes and when expression
fun log(e: Error) = when (e) {
is FileReadError -> {
println("Error while reading file ${e.file}")
}
is DatabaseError -> {
println("Error while reading from database ${e.source}")
}
RuntimeError -> {
println("Runtime error")
}
// the `else` clause is not required because all the cases are covered
}- Sealed classes are abstract by itself, and cannot be instantiated directly. If Sealed classes are abstract by default, why can’t we use an abstract class instead of a sealed class in the first place? Well, the catch here is an abstract class can have their hierarchies anywhere in the project, whereas a sealed class should contain all the hierarchies in the same file.
- Another important advantage of using a sealed class over abstract class is that it helps the IDE to understand the different types involved and thereby helps the user in auto-filling and avoiding spell mistakes.
==================================================================
A coroutine is an instance of a suspendable computation. It is conceptually similar to a thread, in the sense that it takes a block of code to run that works concurrently with the rest of the code. However, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one.
- A coroutine is light-weighted thread.
- Coroutine can be suspend and resumed , while suspending they do not block any threads.
- Every coroutine need to be started in a logical scope with a limited life time. If the life time of scope end/cancel all the coroutine launch with in the scope are not completed yet will be cancelled.
- Coroutine started in the same scope form a hierarchy.
- A parent Job won't complete until all the child job get completed.
- Cancelling a parent job will cancel all the child.
- Cancelling a child will not cancel the parent.
- Ordinary JOB if a child coroutine get failed , exception is propagated upwards and cancel all its jobs.
- Supervisor JOB if a child coroutine get failed , exception is not propagated upwards and do
not
cancel all its jobs.
- The
SupervisorJoballows you to define a hierarchy of coroutines where the failure or cancellation of one coroutine does not affect the others.
- The
- Suspending function , A function with a
suspendmodifier called suspended function.- It is call from another suspended function or coroutine scope.
- It can not be call from normal function.
- We can call the normal function from inside a coroutine.
val scope = CoroutineScope(Job())
fun main() {
runBlocking { // this on execute on Main thread
scope.launch {// create new thread T1
executeTask()
}
delay(200) // on main thread.
}
}
suspend fun executeTask() {
doSomeLongRunningTask()
}
fun doSomeLongRunningTask() {
print("Hello ${Thread.currentThread().name}")
}- launch
- Launches a new coroutine without blocking the main thread.
- it is return the Job object, with this we can control the coroutine by
using
job.join() , job.cancel()and so more. - launch builder by default launch on the thread on which it get called. if it get called from main thread then inside launch the thread will be main.
- Used to launch the coroutine with scope or local scope. like
viewModelScope, fragmentScope or activityScope.GlobalScope.launch()- Global scope means that this scope will live as the life of application. Global coroutine this
one is app level.
- download file
- play music there are some use cases for this.
- This one is not recommended , coz it will always run.
- Async
- Active concurrent Execution
- launch a new coroutine without blocking the main thread. coroutine scope is the scope from where it launched.
GlobalScope.async()-> globally.async-> local- it is return
Deferred<T>object. when we call theasync{}useawait()to get the result. if you do not want the result or return from the method just calldeferredObj.join() and deferredObj.cancel(). Deferredit is aninterfaceand subclass ofJob interface
- runBlocking
- it is blocking the thread, so if we doing something heavy on runBlocking scope on main thread will be blocked.
- used to write the test cases.
To cancel a coroutine , It should be cooperative
delay(),yield(),withContext(),withTimeout(), CoroutineScope.isActivethese are the function that makes a coroutine cooperative.job.cancelAndJoin(), it will cancel the coroutine if coroutine is cooperative , else wait to finish the coroutine.CoroutineScope.isActive()to check the coroutine is active or not.
- We can handle by
try{ } catch{} throw CancellationException. Any function that is cancelable from coroutine package will throw theCancellationException, when it got cancelled. - We can not run a
suspendedfunction from thefinally{}block. - If we want to execute some
suspendfunction fromfinally{}block than warn the code withwthContext(NonCancelable)-
try{
-
- WithTimeOut
- this one is also a
coroutine builderjust like a launch and async. -
- this one is also a
- if the code is not executed with in
200msthenCancellationExceptionwill be thrown. - withTimeoutOrNull
- if the time is not executed in the given time then it returned the value null and "This Value is returned".
-
val returnedValue = withTimeOutOrNull(200){ for(i in 1..1000){ print(i) delay(400) } "This Value is returned" }
- by default coroutine launched in sequential manner.
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis
fun main() = runBlocking {
println("Main Start ${Thread.currentThread().name}")
val time = measureTimeMillis {
val m1 = returnMessageOne()
val m2 = returnMessageTwo()
print("Result $m1 and $m2")
}
println("measureTime in $time ms")
print("End Start ${Thread.currentThread().name}")
}
suspend fun returnMessageOne(): String {
delay(1000)
println("returnMessageOne")
return "MessageOne"
}
suspend fun returnMessageTwo(): String {
delay(1000)
println("returnMessageTwo")
return "MessageTwo"
}- with the help of async we can launch coroutine in concurrent way.
- using launch builder we can also achieve the concurrency.
fun main() = runBlocking {
println("Main Start ${Thread.currentThread().name}")
val time = measureTimeMillis {
val m1 = async { returnMessageOne() }
val m2 = async { returnMessageTwo() }
print("Result ${m1.await()} and ${m2.await()}")
// we can do the same with launch
}
println("measureTime in $time ms")
print("End Start ${Thread.currentThread().name}")
}
- When we call the coroutine using
async{}and not use their result likeresult.get()still the suspended method call inside async{} get called , so it is waste of resources so at thi time the lazy initialization of coroutine scope comes in picture.
fun main() = runBlocking {
println("Main Start ${Thread.currentThread().name}")
val m1 = async(start = CoroutineStart.LAZY) { returnMessageOne() }
val m2 = async(start = CoroutineStart.LAZY) { returnMessageTwo() }
println("Result ${m1.await()} and ${m2.await()}")
print("End Start ${Thread.currentThread().name}")
}
- Coroutine are much less resource intensive than threads. Each time you want to start a new computation you can start new coroutine.
- Use coroutine builder to start a new coroutine
launch , async , runBlocking{}, GlobalScope aysncIt return aDeferred. that means it promise the result sometime in future. Deferred is a generic type that extendsJoblaunchits like fire and forgot. its return aJobobject. we can wait till the join finish by usingjob.join()- add
concurrency, use channels
- Channel is used to share information between different coroutine.
- Writing code with a shared mutable state is quite difficult and error-prone.
- One coroutine can send information to a channel, while other can receive that information from it.
Coroutine send -> ProducerCoroutine Receive -> Consumer- One or multiple coroutine can send and receive the information.
- When many coroutine receive information from the same channel.
each element is handled only once by one of the consumer. Once an element is handled it is removed immediately from the channel. - A Channel is like a queue, in added element at one end and receive from another end.
- have
suspended send() and receive() - Channel is represented by three different interface
SendChannel-> producer , only send the informationReceiveChannel-> consumer , only receive informationCHannel
interface SendChannel<in E> {
suspend fun send(element: E)
fun close(): Boolean
}
interface ReceiveChannel<out E> {
suspend fun receive(): E
}
interface Channel<E> : SendChannel<E>, ReceiveChannel<E>- it receives an element if the channel is not empty; otherwise, it is suspended.
- An unlimited channel is the closest analog to a
queue:
producers can send elements to this channel and it will keep growing indefinitely. The send() call will never be suspended. If the program runs out of memory, you'll get an OutOfMemoryException.The difference between an unlimited channel and a queue is that when a consumer tries to receive from an empty channel, it becomes suspended until some new elements are sent. - By default, a "Rendezvous" channel is created.
- The size of a buffered channel is constrained by the specified number. Producers can send elements
to this channel until the size limit is reached. All of the elements are internally stored. When
the channel is full, the next
sendcall on it is suspended until more free space becomes available.
- The "Rendezvous" channel is a channel without a buffer, the same as a buffered channel with zero
size.
One of the functions (send() or receive()) is always suspended until the other is called.
- A new element sent to the conflated channel
will overwrite the previously sent element, so the receiver will always get only the latest element.The send() call is never suspended
- Channel can be close to indicate that no more elements are coming.
- As we know
channel.close()is close the channel , now in receiver side as we are using for loop to get the items likefor(value in channel)it will get the values until the channel is not close. channel.send()it is a blocking code that means if we sned values in loop from i to 5 then only 1 value will be send if there is know receiver, that meanssend()will be blocked until the value is received from the channel. in other words when a receiver receive() the value than send will ready to send another value.
fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (i in 1..5) channel.send(i * i)
}
channel.close()
launch {
for (value in channel) {
println(value)
}
}
}
- There is a convenient coroutine builder named produce that makes it easy to do it right on producer side, and an extension function consumeEach, that replaces a for loop on the consumer side:
- Here we use the
produce{}coroutine builder that provide and manage the channel for us and return theReceiveChannel<E> consumeEach{}it is used to receive the values from channel. it is extension function ofReceiveChannel<E>
- Pipeline is a pattern where a coroutine produce a possible infinite streams of values.
fun <T, R> ReceiveChannel<T>.map(
coroutineScope: CoroutineScope,
transform: (T) -> R
): ReceiveChannel<R> {
val receiveChannel = this
return coroutineScope.produce {
try {
for (value in receiveChannel) send(transform(value))
} catch (e: CancellationException) {
receiveChannel.cancel()
}
}
}
fun <T, R> map(
coroutineScope: CoroutineScope,
producer: ReceiveChannel<T>,
transform: (T) -> R
): ReceiveChannel<R> = coroutineScope.produce {
for (value in producer) send(transform(value))
}
@OptIn(ExperimentalCoroutinesApi::class)
fun createPipelineOfNumbers(coroutineScope: CoroutineScope, start: Int): ReceiveChannel<Int> =
coroutineScope.produce {
var x = start
while (true) {
send(x++)
delay(500)
}
}
fun consumePileLineData(coroutineScope: CoroutineScope, producer: ReceiveChannel<Int>) =
coroutineScope.launch {
producer.consumeEach {
println(it)
}
}
fun main() = runBlocking {
val numbers = createPipelineOfNumbers(this, 1).map(this) {
it * it * it
}
// consumePileLineData(this, numbers)
// val squareProducer = map(this, numbers) {
// it * it
// }
// consumePileLineData(this, squareProducer)
consumePileLineData(this, numbers)
delay(5000)
numbers.cancel()
}
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.runBlocking
val rendezvousChannel = Channel<String>()
val bufferedChannel = Channel<String>(10)
val conflatedChannel = Channel<String>(CONFLATED)
val unlimitedChannel = Channel<String>(UNLIMITED)
fun main() = runBlocking {
produceChanel().consumeEach {
print("$it ")
}
println()
}
fun CoroutineScope.produceChanel(): ReceiveChannel<Int> = produce {
for (i in 1..5) send(i * i)
}
- We have one producer and many consumer , Send the data to a channel and consumer consume the data , Each consumer consume the data when they are ready to consume the data.
- we have multiple producer and single consumer consume it data.
- Producer fan out
- Consumer Fan-in
- Suppose we have multiple suspending functions , wnt to execute when any one of those is done.
- In Kotlin coroutines, the “select” expression makes it possible to await multiple suspending functions simultaneously and selects the first result that becomes available. It’s a suspending function that is suspended until one of the clauses is either selected or fails
- There’s a scenario in which you want to process “X” thing when anyone from data “A” or “B” becomes available first.
- You are running a few operations concurrently and whichever finishes (or returns result) first, proceed with the result of the winner operation i.e. data race.
- even select is bias that means one time it will select one producer , but it will not forget the value pf previous producer.
- Problem when we have a producer that produce the data in each 100ms and consumer consume the data in each 500 ms, As we know channel is blocked it will send another data after the first ine is received. Like send 1 then it wait to 500 ms to be received and again 1 sec to send and again 5ms to receive.
- we can use the
select{ onSend() }, it will not block the channel it will like it will send the data as the some other channel is available to receive.
fun main() = runBlocking {
val winner = select<String> {
data1().onAwait { it }
data2().onAwait { it }
}
// winner is data1 = Hello coz it will complete in 1000 ms.
}
fun data1() = GlobalScope.async {
delay(1000)
"Hello"
}
fun data2() = GlobalScope.async {
delay(2000)
"World"
}- Fork / Join -> Asynchronous code in java style
- Fork , creation of multiple concurrent execution of branches , each executing a different part of the code simultaneously.
- Join - typically refers to a synchronization point where the execution of the main thread or coroutine waits for the completion of the forked execution branches.
- Async{}.await() coroutine way to write Asynchronous code
- Benefit of Asynchronous code
- Speedup code
- More efficient
fun main() {
val job1 = GlobalScope.launch {
// Code for first concurrent branch
println("Branch 1")
}
val job2 = GlobalScope.launch {
// Code for second concurrent branch
println("Branch 2")
}
// Ensure that the main function doesn't exit before both branches finish executing
runBlocking {
job1.join()
job2.join()
}
}
fun main() {
val job = GlobalScope.launch {
// Some asynchronous work
delay(1000)
println("Coroutine finished")
}
println("Main thread executing...")
// Wait for the coroutine to finish before continuing
runBlocking {
job.join()
println("Coroutine joined")
}
println("Main thread finished")
}
- Coroutine Scope means when you launch a coroutine a coroutine scope created that tell us which kind of coroutine is launched.
launch{this:coroutineScope}same with async and any builder.- Each coroutine has its own coroutine scope.
- The coroutine scope is responsible for the structure and
parent-child relationshipsbetween different coroutines. New coroutines usually need to be started inside a scope
- Every coroutine has a coroutine context. an interface in Kotlin that defines the behavior of a coroutine using a collection of elements
- The important elements of CoroutineContext are:
- Job , CoroutineDispatcher, CoroutineName, CoroutineExceptionHandler
- Context can flow to child coroutine , Child coroutine can inherit the behaviour of parent coroutine.
- CoroutineContext -> launch on parent coroutine context
- Unconfined -> launch on the parent thread context and if delay than resume on some other thread.
- The coroutine context stores additional technical information used to run a given coroutine, like the coroutine custom name, or the dispatcher specifying the threads the coroutine should be scheduled on
- Dispatchers.MAIN
- Dispatchers.IO
- Dispatchers.DEFAULT
- Dispatchers.UNCONFINED
- When the exception occur it cancel itself and propagated the exception to its parent launch.
// In this example we will get the exception that will caught by `handler`.
// Before caught the exception it will complete the Success, because it will delay 400 in first launch.
// if that delay is less 10 then both launch will be cancelled.
val handler = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.e("TAG", "handleException : ${throwable.cause}")
}
lifecycleScope.launch(handler) {
launch {
launch {
delay(400)
throw Exception("Error")
}
}
launch {
delay(100)
println("Success ")
}
}In this Example launch is wrapped by supervisorScope so only that job get exception will be
cancelled and others will be called successfully and print values.
val handler = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.e("TAG", "handleException : ${throwable.cause}")
}
lifecycleScope.launch(Dispatchers.Main + handler) {
supervisorScope {
launch {
launch {
delay(400)
throw Exception("Coroutine 1 Error")
}
}
launch {
delay(100)
println("Coroutine 2 sccess")
}
launch {
delay(400)
println("Coroutine 3 sccess")
}
}
}Suppose we have a coroutine job that is cancel after some time and the network call is in the way than What happen to the coroutine?
- NOTE: - When we hit the
job.cancel(), cancellation required cooperation, when we are doing something that is taking time and like reading list of files , We have to make our coroutine cooperative to make sure the cancelable will works. So we have to check at time of felting file that coroutine is active or not. by usingensureActive()oryield(). - As we launched the coroutine and its doing some long running task job and we cancel the job. so it
will throw the
CancelationExceptionso we need catch that exception specifically. or we need to catch the specif exceptions. - If we catch with
Exceptiononly then it will catch theCancellationExceptionAlso and job is running and doing the jobs but that should not the behaviour. - Check the code in below. Solution.
- Correct way to doing cancel.
lifecycleScope.launch {
val job = launch {
try {
delay(1000)
} catch (e: HttpRetryException) {
e.printStackTrace()
}
// this code will be called because we catch the CancellationException so that will not perform its cancellation.
println("Nope cant print")
}
delay(100)
job.cancel()
}- it is a special function that can be suspended without blocking the main thread.
- Under the Hood, When we call the suspend function then a parameter added in function that
is
Continuation.
suspend fun callHere() {
delay(100)
}
// After Compilation
public static final Object callHere(@NotNull Continuation var1) {
Object $continuation;
// do stuff here
$continuation = new ContinuationImpl(var1)
}-
Continuationis taking care of context of suspension and resume. this have the information when this one is need to resume. just like a callback for you provided by coroutine. -
Its called
Continuation-Passing-StyleCPS its like a Continuation State Machine -
call load -> suspend , state = 1
-
call load and when task is done -> resume state = 0
-
exit.
-
When suspend function return something that means
Work is Completed -
When Scope is cancelled that means all children in that scope also cancelled.
suspend fun work() {
val startTime = System.currentTimeMillis()
var nextPrintTime = startTime
var i = 0
while (i < 5) {
yield() // due to this it will not printr 4, 5
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("Hello ${i++}")
nextPrintTime += 500L
}
}
}
fun main(args: Array<String>) = runBlocking<Unit> {
val job = launch(Dispatchers.Default) {
try {
work()
} catch (e: CancellationException) {
println("Work cancelled!")
} finally {
println("Clean up!")
}
}
delay(1000L)
println("Cancel!")
job.cancel()
println("Done!")
}
o / p
Hello 0
Hello 1
Hello 2
Cancel!
Done!
Work cancelled !
Clean up !
import kotlinx . coroutines . *
fun main(args: Array<String>) = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5 && isActive) {
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("Hello ${i++}")
nextPrintTime += 500L
}
}
// the coroutine work is completed so we can cleanup
println("Clean up!")
}
delay(1000L)
println("Cancel!")
job.cancel()
println("Done!")
}
// o/p
Hello 0
Hello 1
Hello 2
Cancel!
Done!
Clean up !
- suspendCoroutine and suspendCancellableCoroutine are functions used to create suspending functions from callback-based or future-based asynchronous operations.
suspend fun downloadFile(url: String): ByteArray {
return suspendCoroutine { continuation ->
val request = URL(url).openConnection() as HttpURLConnection
request.setRequestProperty("Accept", "application/octet-stream")
request.connectAsync { connection ->
if (connection.responseCode == HttpURLConnection.HTTP_OK) {
val inputStream = connection.inputStream
val bytes = inputStream.readBytes()
continuation.resume(bytes) // Resume with downloaded bytes
} else {
continuation.resumeWithException(IOException("Download failed"))
}
}
}
}- Similar to suspendCoroutine, but provides built-in cancellation support.
suspend fun downloadFileWithCancellation(url: String): ByteArray {
return suspendCancellableCoroutine { continuation ->
val request = URL(url).openConnection() as HttpURLConnection
request.setRequestProperty("Accept", "application/octet-stream")
continuation.invokeOnCancellation {
request.disconnect() // Cleanup on cancellation
}
request.connectAsync { connection ->
if (connection.responseCode == HttpURLConnection.HTTP_OK) {
val inputStream = connection.inputStream
val bytes = inputStream.readBytes()
continuation.resume(bytes) // Resume with downloaded bytes
} else {
continuation.resumeWithException(IOException("Download failed"))
}
}
}
}Must Read. https://medium.com/androiddevelopers/cancellation-in-coroutines-aa6b90163629 https://medium.com/androiddevelopers/coroutines-patterns-for-work-that-shouldnt-be-cancelled-e26c40f142ad https://medium.com/androiddevelopers/coroutines-patterns-for-work-that-shouldnt-be-cancelled-e26c40f142ad https://www.youtube.com/watch?v=w0kfnydnFWI
====================================================================
Generics in Kotlin allow you to write reusable code by creating classes, interfaces, and functions that can work with multiple types. They provide type safety and help in avoiding type-casting errors at runtime
- Class
class FindElement<T>(private val array: Array<T>) {
fun findElement(element: T, foundedElement: (index: Int, element: T?) -> Unit) {
for (i in array.indices) {
if (element == array[i]) {
foundedElement(i, element)
return
}
}
foundedElement(-1, null)
return
}
}
- Methods
fun <T> findElements(
element: T,
array: Array<T>,
foundedElement: (index: Int, element: T?) -> Unit
) {
for (i in array.indices) {
if (element == array[i]) {
foundedElement(i, element)
return
}
}
foundedElement(-1, null)
return
}
- Type parameters can only be
consumed (receive)values. - if we have a class with a given super type generic then we can assignees the sub-type generic of that super class.
- We can use the super type in place of sub type.
- We can use contravariance, where we want the class or interface use generic type as input.
- It allows you to use a more generic subtype where a less generic supertype is expected
interface Persone<in P> {
fun doSomething(p: P) // this will compile.
fun doSomethingAndReturn(p: P): P // Error , compile time
val persone: P // compile type error
}
fun main() {
// val userComparator:Comparable<User> = adminComparator // it is not compile due to contravariance.
val adminComparator: Comparable<AdminUser> = userComparator // this will compile
}
val adminComparator: Comparable<AdminUser> = object : Comparable<AdminUser> {
override fun compareTo(other: AdminUser): Int {
return 1;
}
}
public val userComparator: Comparable<User> = object : Comparable<User> {
override fun compareTo(other: User): Int {
return 1;
}
}
- Parameters can only be
produce (return)values - Covariance is used to produce the items , that means return the item , if we tried to consume them that compiler will show us error at the compiler time itself.
- where you want to return more specific subtypes based on the implementation.
-
List<out E>- E is the type of elements list contains
- The list is covariant in its list type , due to this properties we can do in kotlin.
-
- these line compile in kotlin due to covariance , same will be not compiled in java. Examples :
- here list of user is covariant of its type that is Admin and normal user.
- This not compile
val mutableListOfUser:MutableList<User> = mutableListOfAdminbecause // MutableList<E>E - the type of elements contained in the List. The mutable list is invariant in its element type.
fun main() {
val listOfNormalUser = listOf<NormalUser>()
val lisOfAdminUser = listOf<AdminUser>()
val listOfUser: List<User> = listOfNormalUser
val listOfUser1: List<User> = lisOfAdminUser
}
open class User(name: String)
class AdminUser(name: String) : User(name)
class NormalUser(name: String) : User(name)
// ==========- Here is the Example is use of covariance
- with the help of
@UnsafeVarianceannotation, covariance can consume. but is is not recommended until you are sure.
interface Person<out E> {
fun getPerson(): E // compile cos it is producing
fun setPerson(e: E) // its is not compile due ro covariance property.
fun setPerson(e: @UnsafeVariance E) // this will compile=.
}inline fun <reified T> checkType(value: Any) {
if (value is T) {
println("Value is of type ${T::class.simpleName}")
} else {
println("Value is not of type ${T::class.simpleName}")
}
}- Like when we have same-method name and same type of parameters and but with difference type of element.
//this will not compile, because they have different type of List String , Int with same function name.
fun performList(list: List<String>)
fun performList(list: List<Int>)
// to fix that problem we can use @JvmName(")
@JvmName("performListString")
fun performList(list: List<String>) {
}
@JvmName("performListInt")
fun performList(list: List<Int>) {
}
Inline- We used inline keyword in function that take parameter as lambda. if we will not mark these function as inline then it will take memory and just call like function.
- it cut the body , and paste in function.
- inline function copy past complete code in function body it has issue,
- like we write two function , in below example the second method is nol call. because first one return.as it copy and paste in function so it will act as local return for main().
- to prevent this
crossinline, To prevent to non-local return. noinlineit will treat the lambda as function. also return is non-local.
fun main() {
printMessage {
println("Messagge")
return
}
printMessage { print("SecondMessage") }
}
inline fun printMessage(a: () -> Unit) {
a.invoke()
}
inline fun applyTransformation(
videos: List<Video>,
noinline transformation: (Video) -> Video, // do not want this fun as inline
crossinline onComplete: (List<Video>) -> Unit // prevent non local return
) {
onComplete(videos.map(transformation))
}
- When we required type in side function in generics than we need to use that.
- it is always used with inline.
inline fun <reified T : video> filterList(list: List<Video>): List<T> {
return list.filterIsInstance<T>()
}- We want to restrict the type of genetic types
class PlayVideo<T> where T : Video {
}- FLow is a coroutine that can emit multiple values over a period of times.
- it is cold flow, that means if any one is not collection that flow than it is not emitting.
- flow will be active as there collector will be active in below example ., we have cancel the job that start collection the flow that means we cancel the collector after 5000 ms that why only 4 no will be printed (1..4). After 5000ms the collector will cancel and flow will stop emitting data.
fun generateData() = flow<Int> {
var num = 1
while (true) {
delay(1000)
emit(num++)
}
}.flowOn(Dispatchers.IO)
fun generateFixedFlow() = flow<Int> {
for (i in 1..5) {
delay(100)
emit(i)
}
}
fun main(): Unit = runBlocking {
val coroutineScope = CoroutineScope(Dispatchers.Default)
val job = coroutineScope.launch {
generateData().collect {
println(it)
}
}
delay(5000)
job.cancel()
println("Start collection new")
launch {
generateFixedFlow().collect {
println("collect from 1 $it")
}
}
delay(300)
launch {
generateFixedFlow().collect {
println("collect from 2 $it")
}
}
delay(600)
launch {
generateFixedFlow().collect {
println("collect from 3 $it")
}
}
launch {
generateFixedFlow()
.map { "after map ${it * it}" }
.filter { it == "after map 16" }.collect {
println(it)
}
}
launch {
val result = generateFixedFlow().reduce { accumulator, value -> accumulator + value }
val resultMul = generateFixedFlow().reduce { accumulator, value -> accumulator * value }
println(result)
println(resultMul)
}
launch {
val result = generateFixedFlow().fold(10) { acc, value ->
acc + value
}
println("Fold result $result")
}
}
- transform the flow
transform{}- take(),takeWhile{predicate}
- drop()
flow.filter{ time%2==0}map{time*time}onEachits not transformreduce{accumulator,value->accumulator+value}adding all elements of list.{1,2,3,4,5} = 15fold(100){accumulator,value->accumulator+value}=> 115.flatmapCombine{}flatmapMerge{}-> merge flows concurrently.flatmaplatest{}-> Collection of flow cancel as soon as another values comes available.flatmapConcat{}-> wait for inner flow to complete before starting to collect next values.buffer()Like we have a flow that is ordering the food and another on is consuming the food. so both produces and consumer run on different coroutines.conflate()it will skip. it will provide you the most recent. it will like if we have delay(300) at receiver and 100 at emitter side then we can use conflate it will skip(1,3) and print 0 2,4Zip()-> it will use both flows values while both have different delay to emit the values.- in this example we have two data set one take(5) and another take(10), but due to zip it will call when both emit successfully so only 5 values will be called.
generateData(100)
.take(5)
.zip(generateData(1000).take(10)) { fast, slow ->
println("Fast $fast slow $slow")
fast + slow
}.collect {
println("After Zipping $it")
}Combine()combile the last value available.
generateData(100)
.take(5)
.conflate().collect {
delay(300)
println("Received After conflate $it")
}
// output 1,3,5 print collectLatst{}collecting the latest.Debounce-> it is like we provide time to remove the duplicate or just like we want search on keywoprds so put debounce for 1000transform<>: Flow -> we can use multiple operator in one place like.transform{ emit(it),emit(it*2) }it will emit two flow.flowOn-> change the context of the flow, like thread of flow is performing. we can not change the flow context inside the flow.
- if collector is slower than emitter, may want to buffer the collector
- its like if we emit the values in 100ms delay and collect the values in 500ms so collector is slow. we waste a lot of time. to fix this problem we can use the buffer at the collector side.
fun main() = runBlocking {
val time = measureTimeMillis {
generateFixedFlow()
.take(5)
.buffer(5)
.collect {
delay(500)
println(it)
}
}
println("Time to print $time ms")
}
- it is used to keep the State. its not lifecycle aware , it is similar like live data.
- it is hot flow. it will also do something if there is no collector.
- it has default value
mutableStateFlow(0)stateFlow.update{}it will guaranty that it will update concurrently and thread safe
stateIn()it is an operator that can turn a cold flow or flow to sharedFlow, it will take three parameters1. Scope 2.How To Start 3. ReplaystateIn(scope,SharingStarted,cacheSize)SharingStartedEagerly -> if flow is not starting collection still they emit and drop the values. , lazy and WhileSubscribed()- IF we want to share the flow between multiple collectors than we should use this.
- it is also hot flow.
- we should use collect in place of collectLatest because we do not want to drop any events.
- if we emit shared flow first than collect than it will lost events.
- So first collect the events than emit. means collector should be registered.
- all the one time events just like you do not want to show the error snack-bar again and again, navigation events again and again.
- it is used to combine two flow together.
- Like we have a user , that has some post and need to check isAuthenticated or not.
- combine is called when either of isAuthenticated or user get changed.
isAuthenticated.combine(user) { isAuthenticated, user ->
if (isAuthenticated) user else null
}.combine(post) { user, post ->
createNewObject(user, post)
}.launchIn(viewModelScope)
it will wit for the emission of both flow when both flow will emit then the zip{}. is called.
flow1.zip(flow2) { flow1, floe2 ->
print(flow1, flow2)
}- it will merge all flow into one flow.
- All the should be of same toe else it rerun any type.
merge(flow1, flow2).onEach {
}try{}catch(){}flow.catch{}flow.onCompletion{}check(condition){""Exception"}
- in kotlin we have
kotlin.collectionpackage - Lis
- mutableListOf()
- Map
it copy the original object and not create the new object for the nested one, copy as reference, if original nested object got changed than copied object value get changed
It copy the original object and also create the new copy or nested objects so when we change in the original copy no changes reflect on copied copy.
fun main() {
// it copy the original object and not create the new object for the nested one, copy as reference, if original nested object got changed than copied object value get changed
// shallow copy
val address = AddressTest("Noida")
val original = PersonTest("Amandeep", address)
val copied = original.copy(name = "Tomar")
println(original)
println(copied)
address.city = "Banglore"
println(original)
println(copied) // here copied values of address get changed
println("deep copy")
// deep copy
val address1 = AddressTest("Banglore")
val original1 = PersonTest("Amandeep", address)
val copied1 = original.clone()
println(original1)
println(copied1)
original1.address.city = "New York"
println(original1)
println(copied1)
}
// shallow Copy
data class PersonTest(val name: String, var address: AddressTest) : Cloneable {
public override fun clone(): PersonTest {
return PersonTest(name, address.clone())
}
}
data class AddressTest(var city: String) : Cloneable {
public override fun clone(): AddressTest {
return super.clone() as AddressTest
}
}
* associated()
* associatedBy{}
* map,mapNotNull , mapIndex
* flatten()
* flattenMap{}
* reversed()
* sortedBy{}
* sortedByDescending{}
* sortedWith(Comparator<Int>{x,y->})
* slice
* partition
* mapValues
* mapKeys
* zip
* unzip
* Fold , FoldIndex , FoldRight , FoldRightIndex -> we have support to provide the initial value, like 10 initial and add rest.
* Reduce , ReduceIndex , ReduceRight , ReduceRightIndex -> it start from beginning and initial value is index 1 value. empty list can not perform reduce
* ====== Grouping
* GroupBy , GroupBy(){with new values} // it can be done by some conditions.
* GroupByTo - > Group by first lambda, modify value with second lambda, dump the values to given mutable map
* GroupingBy -> FoldTo -> , like applyingb eachcount() with GroupingBy not groupNy.
* chunked(size)
* windowed{size , steps , isPartialWindow}
* Count , Count{}
* max . maxBy{} , maxByOrNull{}, // this will return person object
* maxOfOrNull{}
* maxWith(Comparator<Int>{x,y->
* when{
* x==1->1
* y==1->1
* else ->y-x
* })
* Min , minBy , minWith(), minByOrNull{} // this will return person object
* ,minOfOrNull{} // this will return value as Int not person object
* sum , sumBy{} , sumByDouble{}
* filter{} , FilterKeys{} , FilterValues{} , FilterIndexed{index,value}
* FilterIsInstance -> Type parameter defines the class instance. None returned because in our list all of them are ints
* Take
* take() , takeWhile() , takeLast() , takeLastWhile()
* Drop
* drop() , dropWhile() , dropLast() , dropLastWhile()
* Element
* elementAt() , elementAtOrNull() , elementAtOrElse(){}
* Get
* get() , getOrElse() , getOrNull()[need to check]
* Map
* get -> [] , getValue() , getOrDefault("1", 10), default value is 10 ,
* getOrPut("1"){10} if we have value at 1 return the value else put 10 on 1.
* find , findLast
* first first{}, last , last{}, firstOrNull , firstOrNull{}, lastOrNull,lastOrNull{}
* indexOf , indexOfFirst , indexOfLast
*
// Unions, distinct, intersections
* Distinct , DistinctBy{}
* Intersect
* Single -> list.single() -> Returns only element or throws.
* singleOrNull() -> Throw safe version of single().
*Union -> list.union(listOf(1,2,3)) -> [1,2,3,4,5]
* onEach
* All , any , none , contains
* isEmpty , isNotEmpty
package com.amandeep.kotlinbasics.kotlin.collectionsinkotlin
val personList = listOf(
Person("Amandeep", company = "Amandeep Tomaer", age = 31),
Person("Amandeep", company = "Amandeep Comany", age = 32),
Person("Amandeep", company = "Amandeep data ", age = 24),
Person("Komal", company = "Amandeep Chauhan", age = 27),
Person("Himanshu", company = "Amandeep Chauhan", age = 25),
Person("Kajol", company = "Amandeep Chauhan", age = 24),
Person("Ekansh", company = "Amandeep Shrivastav", age = 36),
Person("Annu", company = "Amandeep Shrivastav", age = 32),
Person("Gaurav", company = "Gaurav Chauhan", age = 33),
)
val list = listOf(1, 2, 2, 2, 3, 4, 5)
val list2 = listOf("a", "b", "c", "d")
val map = mapOf("a" to 1, "b" to 2, "c" to 3).toMutableMap()
fun transformationsInCollections() {
/*
* associated()
* associatedBy{}
* map,mapNotNull , mapIndex
* flatten()
* flattenMap{}
* reversed()
* sortedBy{}
* sortedByDescending{}
* sortedWith(Comparator<Int>{x,y->})
* slice
* partition
* mapValues
* mapKeys
* zip
* unzip
* */
val associatedList = list.associateBy { it }
println(associatedList)
val personAssociatedList = personList.associateBy { it.name }
println(personAssociatedList)
val associate = list.associateBy { it }
println("associateBy $associate")
val updatedList = list.map { it + 1 }
println("Use of Map $updatedList")
val mapIndex = list.mapIndexed { index, it ->
if (index == 2) it * 2 else null
}
println("mapIndex $mapIndex")
val listOfPartitions = list.partition { it and 1 == 0 }
println("listOfPartitions $listOfPartitions")
val sliceList = list.slice(1..5)
println("sliceList $sliceList")
val flattenList = listOf(list, list2).flatten()
println("flattenList $flattenList")
val flattenListWithMap = listOf(list2, list).flatMap {
it + list2
}
println("flattenListWithMap $flattenListWithMap")
val mapOfkeys = map.mapKeys { (key, values) -> key + values } // {a1=1, b2=2, c3=3}
println(mapOfkeys)
val mapOfValues =
map.mapValues { (key, Values) -> Values * 2 } // {a=2, b=4, c=6} // coz multiply by 2.
println(mapOfValues)
val zippedList = list.zip(list2)
println("ZippedList $zippedList") // [(1, a), (2, b), (2, c), (2, d)]
val unZipList = zippedList.unzip()
println("unZipList $unZipList") // ([1, 2, 2, 2], [a, b, c, d])
println("flatten array of arrays")
arrayOf(arrayOf(1, 2, 3, 4), arrayOf(5, 6, 7, 8), arrayOf(9, 10, 11, 12)).flatten().forEach {
print(it)
}
personList.toSortedSet(compareBy { it.name })
}
fun aggregatorsIsCollections() {
/**
* Fold , FoldIndex , FoldRight , FoldRightIndex -> we have support to provide the initial value, like 10 initial and add rest.
* Reduce , ReduceIndex , ReduceRight , ReduceRightIndex -> it start from beginning and initial value is index 1 value. empty list can not perform reduce
* ====== Grouping
* GroupBy , GroupBy(){with new values} // it can be done by some conditions.
* GroupByTo - > Group by first lambda, modify value with second lambda, dump the values to given mutable map
* GroupingBy -> FoldTo -> , like applyingb eachcount() with GroupingBy not groupNy.
* chunked(size)
* windowed{size , steps , isPartialWindow}
* Count , Count{}
* max . maxBy{} , maxByOrNull{}, // this will return person object
* maxOfOrNull{}
* maxWith(Comparator<Int>{x,y->
* when{
* x==1->1
* y==1->1
* else ->y-x
* })
* Min , minBy , minWith(), minByOrNull{} // this will return person object
* ,mixOfOrNull{} // this will return value as Int not person object
* sum , sumBy{} , sumByDouble{}
* */
println()
val list = listOf(1, 2, 3, 4, 5)
val reduce = list.reduce { acc, value -> acc + value }
println("Reduce $reduce") // 15
val reduceIndexed = list.reduceIndexed { index, acc, value ->
if (index and 1 == 0) acc + value else acc
}
println("list.reduceIndexed{index,acc,value} = $reduceIndexed") // 9
val reduceRight = list.reduceRight { value, acc -> value + acc }
println("reduceRight $reduceRight") // 15 {5+4+3+2+1}
val reduceRightIndex =
list.reduceRightIndexed { index, i, acc -> if (index == 2) acc else i + acc }
println("reduceRightIndex $reduceRightIndex") // 3 is skipped , and is 12.
// val reduceWithEmptyList = emptyList<Int>().reduce { acc, i -> acc + i }
// println(reduceWithEmptyList) // Empty collection can't be reduced. UnsupportedOperationException
// fold
val fold = list.fold(10) { acc, value -> value + acc }
println("fold with initial value of 10 $fold") //25
// same all other methods like foldIndexed(10){index , acc , value ->{}}
val rightFold = list.foldRight(10) { value, acc ->
print(value)
value + acc
}
println("rightFold $rightFold")
// same for foldRightIndexed(10){index, value,acc-> }
// Grouping
personList.groupBy { it.name }.forEach {
println("${it.key} -> ${it.value.count()}") // Amandeep -> 3 for all values
}
val groupingBy = personList.groupingBy { it.name.first() }.eachCount()
println("groupingBy $groupingBy") // groupingBy {A=4, K=2, H=1, E=1, G=1}
personList.groupingBy { it.company }.eachCount()
val maxAgeInList = personList.maxByOrNull { it.age } // this will return person object
val minAgeInList = personList.minByOrNull { it.age }// this will return person object
val minByAgeInList = personList.minBy { it.age }
val maxByAgeInList = personList.maxBy { it.age }
val maxOfAgeInList =
personList.maxOfOrNull { it.age } // this will return value as Int not person object
val minOfAgeInList =
personList.minOfOrNull { it.age }// this will return value as Int not person object
println("maxAgeInList in personList $maxAgeInList \n maxAgeInList in personList $minAgeInList ")
println("maxOfAgeInList in personList $maxOfAgeInList \n minOfAgeInList in personList $minOfAgeInList ")
println("maxByAgeInList in personList $maxByAgeInList \n minByAgeInList in personList $minByAgeInList ")
val partionList = personList.partition { it.age <= 24 }
println(partionList)
// here we need to find the no if person whoes age is <=24
val countOfAgeLessThanEqualTo24 = personList.count { it.age <= 24 }
println("countOfAgeLessThanEqualTo24 $countOfAgeLessThanEqualTo24 ")
// print the average of the age < = 34
val personsListOfAgeLessThanEqualOf34 = personList.partition { it.age <= 34 }.first.map {
it.age
}
println(personsListOfAgeLessThanEqualOf34.sorted())
println("avereage = ${personsListOfAgeLessThanEqualOf34.average()}")
println("Average Age is ${personsListOfAgeLessThanEqualOf34.sum() / personsListOfAgeLessThanEqualOf34.size}")
// chunk
val chuncked = list.chunked(2)
println(chuncked)
val windowed = list.windowed(2, 1, false)
println(windowed)
val windowed2 = list.windowed(2, 2, false)
println(windowed2)
}
fun filteringTakeAndDropCollections() {
/***
* filter{} , FilterKeys{} , FilterValues{} , FilterIndexed{index,value}
* FilterIsInstance -> Type parameter defines the class instance. None returned because in our list all of them are ints
* Take
* take() , takeWhile() , takeLast() , takeLastWhile()
* Drop
* drop() , dropWhile() , dropLast() , dropLastWhile()
* Element
* elementAt() , elementAtOrNull() , elementAtOrElse(){}
* Get
* get() , getOrElse() , getOrNull()[need to check]
* Map
* get -> [] , getValue() , getOrDefault("1", 10), default value is 10 ,
* getOrPut("1"){10} if we have value at 1 return the value else put 10 on 1.
* */
list.filter { it < 4 }.distinct().forEach { print(it) }
println()
personList.filter { it.name.startsWith("Am") }.map { it.name + it.age }
.forEach { print(it.plus(" ")) }
println()
map.filterKeys { it != "a" }.values.forEach { print(it) }
println()
map.filterValues { it != 2 }.map { it.key + it.value }.forEach { print(it) }
println()
list.filterIndexed { index, i -> index == 2 }.forEach { print(it) }
// take It will take from list
println()
list.take(3).forEach { print(it) }
println()
list.takeLast(3).forEach { print(it) }
println()
list.takeWhile { it < 4 }.forEach { print(it) }
println()
list.takeLastWhile { it > 4 }.forEach { print(it) }
// drop it will drop from the list
println()
list.distinct().drop(2).forEach { print(it) } // 345
println()
list.distinct().dropWhile { it < 5 }.forEach { print(it) } // 5
println()
list.distinct().dropLast(2).forEach { print(it) } // 123
// Element At
val elementAt2 = list.distinct().elementAt(2)
println("elementAt2 = $elementAt2") // 3
val elementAtOrElse = list.elementAtOrElse(10) { -1 } // -1
println("elementAtOrElse = $elementAtOrElse")
val elementOrNull = list.elementAtOrNull(10)
println("elementOrNull = $elementOrNull") // null
// map function
println("map function")
map.values.forEach { print(it) } // 123
println()
val values = map.getOrDefault("d", 1) // 1
println(values)
val newMap = map.getOrPut("e") { 5 }
println(newMap)
map.values.forEach { print(it) } //123 because it is immutable map
}
fun findInCollections() {
// finding
/**
* find , findLast
* first first{}, last , last{}, firstOrNull , firstOrNull{}, lastOrNull,lastOrNull{}
* indexOf , indexOfFirst , indexOfLast
* */
// Unions, distinct, intersections
/**
* Distinct , DistinctBy{}
* Intersect
* Single -> list.single() -> Returns only element or throws.
* singleOrNull() -> Throw safe version of single().
*Union -> list.union(listOf(1,2,3)) -> [1,2,3,4,5]
*
* */
println()
val find = list.find { it > 3 }
println("find $find")
val findLast = list.distinct().findLast { it > 3 }
println(findLast)
val first = list.distinct().first()
println("first $first") // 1
val firstPredicate = list.distinct().first { it > 2 }
println("firstPredicate $firstPredicate") // 3
val firstOrNull = list.distinct().firstOrNull { it > 10 }
println("firstOrNull $firstOrNull") // null
val last = list.distinct().last()
println("last $last") // 5
val lastPredicate = list.distinct().last { it > 2 }
println("lastPredicate $lastPredicate") // 5
val lastOrNull = list.distinct().lastOrNull { it < 2 }
println("lastOrNull $lastOrNull") // 1
// single
val single = listOf<Int>(10).singleOrNull()
println("single $single") // 10
// intercesion
list.intersect(listOf(1, 2, 3)).forEach { print(it) } // 123
println()
list.union(listOf(6, 7, 8)).forEach { print(it) } // 12345678
}
fun actionAndCheckOnList() {
/** forEach , forEachIndexed
* onEach
* All , any , none , contains
* isEmpty , isNotEmpty
*/
println()
list.forEachIndexed { index, i -> print(i) }
println("OnEach")
list.onEach {
print(it)
}
println()
val allDigit = list.map { it.toChar() }.all { it.isDigit() } // false // not working with list
val isAnyDigit = list.none { it.toChar().isLetter() } // true
val isAnyCharIsDigit = list.any { it.toChar().isDigit() } // false
println("allDigit $allDigit , isNoneDigitLetter = $isAnyDigit , isAnyCharIsDigit $isAnyCharIsDigit")
val string = "abc1"
val isAnyDigitHere = string.toCharArray().any { it.isDigit() }
println(isAnyDigitHere)
}
fun main() {
transformationsInCollections()
aggregatorsIsCollections()
filteringTakeAndDropCollections()
findInCollections()
actionAndCheckOnList()
}
data class Person(val name: String, val age: Int, val company: String)