- vừa được xem lúc

Kotlin Flow cheat sheet phần 2: Flow

0 0 6

Người đăng: Henry Techie

Theo Viblo Asia

Tiếp nối serie Kotlin cheat sheet, chúng ta cùng đi đến với cheat sheet tiếp theo dành cho Flow.

Kotlin Flow là một API mạnh mẽ giúp quản lý luồng dữ liệu bất đồng bộ một cách rõ ràng và dễ dàng. Trong phần 2 này, chúng ta sẽ khám phá Flow từ cơ bản đến nâng cao, tìm hiểu cách tạo, chuyển đổi và thu thập các luồng dữ liệu, cũng như các best practice để áp dụng trong dự án Android của bạn.

Bạn có thể đọc toàn bộ serie tại đây:

Flow

Nguyên tắc chính

  • Là một cold stream.
  • Hỗ trợ sẵn structured concurrency.
  • Tác vụ cuối cùng của flow được gọi là tác vụ terminal (collect, first… ).
  • Một flow có thể có các tác vụ trung gian để sửa đổi flow (map, onEach, flatMapLastest… ).
  • Terminal operation là suspend và yêu cầu một scope.
  • Các Exception chưa được bắt sẽ ngay lập tức cancel một flow và collect sẽ throw lại Exception đó.
  • Theo mặc định, context của flow sẽ lấy từ context mà collect được gọi.

Kết hợp các flow với nhau

merge, combinezip là các hàm trung gian cho phép chúng ta kết hợp 2 (hoặc nhiều) flow thành 1. Vậy điểm khác biệt chính giữa 3 hàm đó là gì?

merge

  • Không sửa đổi bất kỳ phần tử nào.
  • Các phần tử được emit ngay khi chúng được tạo ra, chúng ta không đợi flow khác để tạo ra giá trị.
  • Sử dụng nó khi bạn có nhiều nguồn event sẽ tạo ra cùng một action.

flowA emit: 1

flowB emit: 2

flowA emit: 3

merge(flowA, flowB) tạo ra 1, 2, 3

zip

  • Kết hợp các phần tử từ các flow khác nhau để tạo ra giá trị mới.
  • Chúng ta cần một hàm để xác định cách các phần tử được kết hợp với nhau.
  • Chúng ta cần đợi mỗi flow emit một giá trị để có thể tạo cặp.
  • Các phần tử chỉ có thể là một phần của một cặp.
  • Các phần tử còn lại không có cặp sẽ bị mất.

flowA emit: 1

flowB emit: 2

flowA emit: 4

flowA.zip(flowB) {fA, fB -> fA + fB } tạo ra 3 (1+2 = 3, còn 4 từ flowA sẽ bị loại bỏ)

combine

  • Kết hợp các phần tử từ các flow khác nhau để tạo ra giá trị mới.
  • Chúng ta cần một hàm xác định cách các phần tử được kết hợp với nhau.
  • Chúng ta cần đợi flow chậm hơn emit giá trị lần đầu tiên trước khi tạo phần tử mới.
  • Khi một flow tạo ra một phần tử mới, nó sẽ thay thế phần tử trước đómột giá trị mới sẽ được emit ngay lập tức (chúng ta không đợi mỗi flow emit một phần tử mới).

flowA emit: 1

flowB emit: 2

flowA emit: 3

flowA.combines(flowB) { fA, fB -> fA + fB } tạo ra 3 (1+2 = 3) rồi 5 (3+2 = 5, trong đó phần tử 3 đã thay thế cho 1 trước đó)

Sự khác biệt giữa fold và scan

Cả foldscan kết hợp tất cả các giá trị do một flow emit thành một phần tử bằng cách áp dụng thao tác kết hợp các giá trị lại với nhau.

  • fold là một tác vụ terminal. Nó suspend cho đến khi flow hoàn thành và tạo ra giá trị cuối cùng
  • scan là một tác vụ trung gian và tạo ra tất cả các giá trị trung gian
val myflow = flowOf(1, 2, 3, 4)
myFlow.fold(0) { acc, newElement -> acc + newElement } // tạo ra 10 myFlow.scan(0) { acc, newElement -> acc + newElement } // tạo ra 1, 3 (1+2), 6 (3+3), 10 (6+4)

flatMapConcat, flatMapMerge và flatMapLatest

  • Chúng đều là những tác vụ trung gian
  • Chúng biến đổi các phần tử được emit bởi flow ban đầu bằng cách áp dụng một flow khác lên phần tử đó và trả về một flow khác
myFlowA.flatMapConcat { fA -> myFlowB(fA) } // giá trị trả về do flow B tạo ra

flatMapConcat

  • Chuyển đổi từng giá trị được emit thành một flow và nối các flow kết quả một cách tuần tự.
  • Emit hoàn toàn các giá trị từ inner flow đầu tiên trước khi bắt đầu flow tiếp theo.
  • Use Case: khi bạn cần xử lý các flow bên trong theo thứ tự, không bị chồng chéo.

flatMapMerge

  • Chuyển đổi từng giá trị được emit thành một flow và hợp nhất các flow kết quả một cách đồng thời.
  • Emit các giá trị từ tất cả các inner flow khi chúng có sẵn, có khả năng không theo thứ tự.
  • Use Case: khi bạn muốn xử lý đồng thời các flow bên trong và không quan tâm đến thứ tự của các giá trị được emit.

flatMapLatest

  • Chuyển đổi từng giá trị được emit thành một flow, hủy các flow trước đó khi một giá trị mới đã được emit, và emit các giá trị từ flow mới nhất.
  • Chỉ flow mới nhất được hoạt động và các giá trị của nó được emit. Các flow trước đó bị hủy bỏ.
  • Use Case: khi bạn chỉ quan tâm đến giá trị mới nhất và muốn hủy các thao tác trước đó.
data class User(val id: Int, val name: String)
data class UserDetails(val userId: Int, val address: String) fun fetchUserData(): Flow<User> = flow { emit(User(1, "Alice")) delay(500) emit(User(2, "Bob")) delay(500) emit(User(3, "Charlie"))
} fun fetchUserDetails(userId: Int): Flow<UserDetails> = flow { delay(1000) // Giả lập network delay emit(UserDetails(userId, "$userId's address"))
} // flatMapConcat
fetchUserData() .flatMapConcat { user -> fetchUserDetails(user.id) } .collect { userDetails -> println("flatMapConcat: ${userDetails}") }
// Mỗi thông tin user được trả về tuần tự.
// flatMapConcat: UserDetails(userId=1, address=1's address)
// flatMapConcat: UserDetails(userId=2, address=2's address)
// flatMapConcat: UserDetails(userId=3, address=3's address) // flatMapMerge
fetchUserData() .flatMapMerge { user -> fetchUserDetails(user.id) } .collect { userDetails -> println("flatMapMerge: ${userDetails}") }
// Thông tin user có thể bị xen kẽ do trả về đồng thời.
// flatMapMerge: UserDetails(userId=1, address=1's address)
// flatMapMerge: UserDetails(userId=2, address=2's address)
// flatMapMerge: UserDetails(userId=3, address=3's address) // flatMapLatest
fetchUserData() .flatMapLatest { user -> fetchUserDetails(user.id) } .collect { userDetails -> println("flatMapLatest: ${userDetails}") }
// Chỉ thông tin của user cuối cùng được trả về
// do user mới sẽ cancel fetch trước đó.
// flatMapLatest: UserDetails(userId=3, address=3's address)

Chuyển đổi function thành Flow

val function = suspend { // đây là biểu thức lambda suspend // định nghĩa hàm ở đây
} function.asFlow()

Hoặc

suspend fun myFunction(): Flow<T> { // định nghĩa hàm ở đây
} ::myFunction.asFlow()

Tạo flow tạo ra các phần tử trước khi chúng ta subscribe

Hàm channelFlow tạo ra sự kết hợp giữa flow và channel. Nó tạo ra một hot stream data nhưng cũng implement Flow interface.

val myChannelFlow = channelFlow { val myData = // fetch dữ liệu tại đây send(myData)
} suspend fun fetchData() { myData.first()
}

Sửa đổi context của Flow

myFlow.flowOn(Dispatchers.IO) // Hoặc myFlow.flowOn(CoroutineName( "NewName" ))

Tránh lồng nhau khi khởi chạy flow

// thay vì
viewModelScope.launch { myFlow.collect()
} // làm như này
myFlow.launchIn(viewModelScope)

Cảm ơn các bạn đã đọc đến đây, cùng chờ đón những phần tiếp theo nhé.

Reference

🔔 Blog: henrytechie.xyz

☕️ Facebook: Henry Techie

☁️ TikTok: @henrytechie

Bình luận

Bài viết tương tự

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 1: Làm quen cô nàng Flutter

Lời mở đầu. Gần đây, Flutter nổi lên và được Google PR như một xu thế của lập trình di động vậy.

0 0 277

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 3: Lột trần cô nàng Flutter, BuildContext là gì?

Lời mở đầu. Màn làm quen cô nàng FLutter ở Phần 1 đã gieo rắc vào đầu chúng ta quá nhiều điều bí ẩn về nàng Flutter.

0 0 201

- vừa được xem lúc

[Android] Hiển thị Activity trên màn hình khóa - Show Activity over lock screen

Xin chào các bạn, Hôm nay là 30 tết rồi, ngồi ngắm trời chờ đón giao thừa, trong lúc rảnh rỗi mình quyết định ngồi viết bài sau 1 thời gian vắng bóng. .

0 0 103

- vừa được xem lúc

Tìm hiểu Proguard trong Android

1. Proguard là gì . Cụ thể nó giúp ứng dụng của chúng ta:. .

0 0 94

- vừa được xem lúc

Làm ứng dụng học toán đơn giản với React Native - Phần 6

Chào các bạn một năm mới an khang thịnh vượng, dồi dào sức khỏe. Lại là mình đây Đây là link app mà các bạn đang theo dõi :3 https://play.google.com/store/apps/details?id=com.

0 0 61

- vừa được xem lúc

20 Plugin hữu ích cho Android Studio

1. CodeGlance. Plugin này sẽ nhúng một minimap vào editor cùng với thanh cuộn cũng khá là lớn. Nó sẽ giúp chúng ta xem trước bộ khung của code và cho phép điều hướng đến đoạn code mà ta mong muốn một cách nhanh chóng.

0 0 312