KotlinConf 2018
Android ฤ
Akira Iwaya, LINE Corp.
1
Slide 2
Slide 2 text
Who am I
• ؠ୩໌ɹ(@hoshi_gaki)
• LINE LIVE Android
• ϦϚΠϯ͘Μ
• αʔόʔαΠυKotlinͷݟʗKotlin
ͰClova Skill Awardઓ0
0 https://engineering.linecorp.com/ja/blog/detail/346
2
Slide 3
Slide 3 text
3
Slide 4
Slide 4 text
LINE LIVE
• 30% Kotlin and increasing
• In LINE app as submodule
• MVVM + Partially Redux
• Dagger2, RxJava2, AutoDispose, ExoPlayer, ...
4
Android sessions at KotlinConf
2018
• Android Suspenders (Christ Banes)
• Shaping your app's architecture with Kotlin and Architecture
Components
• Android KTX: A dash of Kotlin makes all the difference!
6
Slide 7
Slide 7 text
7
Slide 8
Slide 8 text
Android Suspenders2
Christ Bane
• Why coroutines
• How to use coroutines in Android
• Jobs, Scopes, Reactivity
• Cancellations, Exceptions, Dispatchers, Channels (skip)
• Android callback -> suspend functions (skip)
2 https://chris.banes.me/talks/2018/android-suspenders/
8
How to use coroutines in Android
class ShowDetailsViewModel: ViewModel() {
val showRepository: ShowRepository = // ...
val state: LiveData
init {
refresh()
}
private fun refresh() {
launch {
showRepository.updateShow(showId)
// update view state
}
}
}
12
Slide 13
Slide 13 text
Cancellation
class ShowDetailsViewModel: ViewModel() {
private var updateShowJob: Job? = null
private fun refresh() {
updateShowJob = launch {
showRepository.updateShow(showId)
// update view state
}
}
override fun onCleared() {
updateShowJob?.cancel()
}
}
13
Slide 14
Slide 14 text
!
class ShowDetailsViewModel: ViewModel() {
private var updateShowJob: Job? = null
private var updateEpisodesJob: Job? = null
private var updateCastJob: Job? = null
private fun refresh() {
updateShowJob = launch { ... }
updateEpisodesJob = launch { ... }
updateCastJob = launch { ... }
}
override fun onCleared() {
updateShowJob?.cancel()
updateEpisodesJob?.cancel()
updateCastJob?.cancel
}
}
14
Slide 15
Slide 15 text
Jobs can have parent-child relationship !
private val job = Job()
private fun refresh() {
launch(parent = job) {
showRepository.updateShow(showId)
// update view state
}
}
override onCleared() {
job.cancel()
}
15
Slide 16
Slide 16 text
launch(parent = job) {
updateShow(showId)
}
suspend fun updateShow(showId: Long) {
val local = async { // no parent
localShowStore.getShow(showId)
}
val remote = async [ // no parent
remoteSource.getShow(showId)
]
val merged = mergeShow(local.await(), remote.await())
localShowStore.saveShow(merged)
}
16
Slide 17
Slide 17 text
CoroutineScope
• Two coroutines does not get cancelled when parent job is
cancelled !
• launch, async is deprecated in v0.26
• launch, async becomes instance method of
CoroutineScope
• Must provide default context
17
Slide 18
Slide 18 text
class ShowDetailsViewModel: ViewModel(), CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private fun refresh() {
launch(context = coroutineContext) {
showRepository.updateShow(showId)
...
}
}
override onCleared() {
super.onCleared()
job.cancel()
}
}
18
Slide 19
Slide 19 text
!
suspend fun updateShow(showId: Long) = coroutineScope {
val local = async { // will be cancelled on job.cancel()
localShowStore.getShow(showId)
}
val remote = async [ // will be cancelled on job.cancel()
remoteSource.getShow(showId)
]
val merged = mergeShow(local.await(), remote.await())
localShowStore.saveShow(merged)
}
19
Slide 20
Slide 20 text
Reactivity
Coroutines can replace Single / Maybe / Completable
interface RemoteService {
@GET("/trendingshows")
fun trendingShows(): Single>
}
service.trendingShows()
.scheduleOn(Schedulers.io())
.subscribe(::onTrendingLoaded, ::onError)
20
Slide 21
Slide 21 text
With kotlinx-coroutines-rx2
interface RemoteService {
@GET("/trendingshows")
fun trendingShows(): Single>
}
val shows = withContext(IO) {
service.trendingShows().await()
}
21
Slide 22
Slide 22 text
Coming to Retrofit soon
interface RemoteService {
@GET("/trendingshows")
suspend fun trendingShows(): List
}
val shows = withContext(IO) {
service.trendingShows()
}
22
Slide 23
Slide 23 text
Related sessions
• Exploring Coroutines in Kotlin3
• Kotlin Coroutines in Practice4
• Coroutines and Reactive Programming - friends or foes?5
5 https://youtu.be/yoLh4sd1CWI
4 https://youtu.be/a3agLJQ6vt8
3 https://youtu.be/jT2gHPQ4Z1Q
23
Slide 24
Slide 24 text
24
Slide 25
Slide 25 text
Shaping your app's architecture with Kotlin and Architecture Components6
Florina Muntenescu
• Why using coroutines
• Where to using coroutines
• How to using coroutines
• Java
• Type Safety - Inner class (Kotlin 1.3)
6 https://youtu.be/Sy6ZdgqrQp0
25
Slide 26
Slide 26 text
Why using coroutines
• Ease of learning
• Single/Maybe/Completable -> suspend fun
• Streams of data (Channel)
26
Slide 27
Slide 27 text
Where to use coroutines
27
Slide 28
Slide 28 text
28
Slide 29
Slide 29 text
How to using coroutines - API
retrofit2-kotlin-coroutines-adapter
@POST("api/v2/comments")
fun comment(@Body comment: NewCommentRequest): Deferred>
• RemoteDataSource returns Result
sealed class Result {
data class Success(val data: T): Result()
data class Error(val exception: Exception): Result
}
29
Slide 30
Slide 30 text
class CommentsRemoteDataSource(private val Service: RetrofitService) {
suspend fun comment(commentBody: String) = safeApiCall(
call = { postComment(commentBody) },
errorMessage = "Unable to post commment"
)
}
suspend fun safeAPiCall(
call: suspend () -> Result // suspend lambda
errorMessage: String
): Result = return try {
call()
} catch(e: Exception) {
Result.Error(IOException(errorMessage, e))
}
30
Slide 31
Slide 31 text
Java
31
Slide 32
Slide 32 text
class ShotsRepository(private val dataSource: XXXDataSource) {
suspend fun search(query: String): Result> =
remoteDataSource.search(query)
}
32
Slide 33
Slide 33 text
shotsRepository.search(query,
new Continuation>>() {
@NotNull
@Override
public CoroutineContext getContext() {
return null;
}
@Override
public void resume(Result> result) {}
@Override
public void resumeWithException(@NotNull Throwable throwable) {}
}
33
Slide 34
Slide 34 text
A Good enough compromise - Callback
class ShotsRepository(private val dataSource: XXXDataSource) {
private val inflight = mutableMapOf()
fun search(
query: String,
onResult: (Result>) -> Unit)
: Result> {
inflight[id] = launchSearch(query, onResult)
}
fun cancel() {
inflight.values.forEach { it.cancel() }
inflight.clear()
}
}
34
Slide 35
Slide 35 text
Type Safety - Inline Class
class CommentsRepository(...) {
suspend fun postStoryComment(
body: String,
storyId: Long,
userId: Long
): Result { ... }
suspend fun postReplyComment(
body: String,
parentCommentId: Long,
userId: Long
): Result
Slide 36
Slide 36 text
Type Safety - Inline Class
class CommentsRepository(...) {
suspend fun postStoryComment(
body: String,
storyId: StoryId,
userId: Long
): Result { ... }
suspend fun postReplyComment(
body: String,
parentCommentId: CommentId,
userId: Long
): Result
Slide 37
Slide 37 text
data class StoryId(val id: Long)
• Extra object creation occurs (Runtime overhead)
• Primitive types are heavily optimized by the runtime
inline class StoryId(val id: Long)
• From Kotlin 1.3
• An inline class must have a single property initialized in the
primary constructor
37
Slide 38
Slide 38 text
UseCase - invoke operator
• A use case should have single responsibility
• UseCase should have only one public method
class PostStoryCommentUseCase(repository: XXXRepository) {
suspend fun postStoryComment(body: String, id: Long): Result
}
38
Slide 39
Slide 39 text
• By implementing invoke operator
• Callers doesn't care how this is implemented, even nor
method name
class PostStoryComment(...) {
suspend operator fun invoke(body: String, id: Long): Result
}
// Caller side
val postStoryComment = ...
val result = postStoryComment(comment, id)
39
Slide 40
Slide 40 text
bit.ly/livedata-events
40
Slide 41
Slide 41 text
Modular, extensible architecture
based on the
architecture components and
Kotlin
41
Slide 42
Slide 42 text
42
Slide 43
Slide 43 text
Android KTX: A dash of Kotlin makes all the difference!7
Dan Kim
• KTX is 1.0 with Android X (skip)
• Various extensions (1ݸ)
• How to contribute AOSP (skip)
7 https://www.youtube.com/watch?v=LP3PaPrIFHo
43
Slide 44
Slide 44 text
// added in API level 11
view.addOnLayoutChangeListener(object: View.OnLayoutChangeListener {
override fun onLayoutChange(
view: View?
left: Int,
top: Int,
right: Int,
bottom: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
doSomething()
}
})
44