Bạn đã bao giờ đứng trước màn hình, nhìn chằm chằm vào biểu tượng loading quay vòng quay vòng như một chiếc chong chóng bị kẹt, chỉ vì một hàm đang bận rộn tính toán một đống thứ? Đợi mãi, đợi mãi... đến khi nó xong xuôi thì bạn đã đi pha xong tách cà phê thứ hai rồi.
Đây là câu chuyện buồn về việc xử lý dữ liệu bất đồng bộ theo cách "truyền thống". Các suspend function của chúng ta rất giỏi, rất đáng tin cậy, nhưng đôi khi lại hơi... "cứng nhắc". Giống như một anh shipper, dù đã lấy được món hàng đầu tiên, anh ấy vẫn phải chờ đến khi có đủ 3 món mới chịu giao cho bạn.
Hãy nhìn vào ví dụ này:
suspend fun getItems(): List<String> { return buildList { println("Đang chuẩn bị Món A...") add("Món A") delay(1.seconds) // Ngủ một giấc println("Đang chuẩn bị Món B...") add("Món B") delay(1.seconds) // Ngủ thêm giấc nữa println("Đang chuẩn bị Món C...") add("Món C") delay(1.seconds) // Zzz... }
} fun main() = runBlocking { println("Bắt đầu chờ đợi...") val items = getItems() println("Cuối cùng cũng có hàng! In ra thôi:") items.forEach { println(it) }
}
Bạn chạy đoạn code trên và... thôi rồi, bạn lại phải đi pha cà phê tiếp. Sau 3 giây chờ đợi, màn hình mới bắt đầu hiện ra kết quả. Toàn bộ 3 món hàng đến cùng một lúc, khiến bạn vừa mừng vừa... giận.
Flow: "Người yêu lý tưởng" trong thế giới bất đồng bộ Và rồi Flow xuất hiện, như một "nàng thơ" trong thế giới lập trình. Flow không bắt bạn phải chờ. Thay vì giao tất cả hàng hóa cùng một lúc, nó sẽ ship luôn cho bạn từng món một ngay khi nó sẵn sàng. Bạn nhận được món đầu tiên, có thể dùng ngay. Rồi một lúc sau, món thứ hai đến. Rất nhanh, rất tiện, không lãng phí thời gian của nhau.
Cùng xem ví dụ trên sau khi áp dụng Flow nhé:
fun getItemsFlow(): Flow<String> { return flow { println("Đang phát ra Món A...") emit("Món A") // Ship món đầu tiên delay(1.seconds) println("Đang phát ra Món B...") emit("Món B") // Ship món thứ hai delay(1.seconds) println("Đang phát ra Món C...") emit("Món C") // Ship nốt món cuối delay(1.seconds) }
} fun main() = runBlocking { println("Bắt đầu chờ đợi...") getItemsFlow().collect { item -> println("Đã nhận được: $item") // Phản hồi ngay và luôn! } println("Hết hàng rồi, hạnh phúc quá!")
}
Kết quả lần này thì khác hẳn: Bạn nhận được "Món A" gần như ngay lập tức. Sau 1 giây, "Món B" tới. Và sau 1 giây nữa, "Món C" cũng có mặt. Sẽ không còn cảnh chờ đợi mòn mỏi nữa. Khả năng xử lý từng phần tử ngay khi chúng có sẵn chính là sức mạnh cốt lõi của flow đấy.
Flow cũng có "tính cách" riêng đấy nhé! Giống như con người, flow cũng có hai "tính cách" chính:
Cold Flow: Đây là một em người yêu hơi "chảnh chọe" một chút. Em ấy chỉ bắt đầu hành động khi có người "tán tỉnh" (tức là khi có một collector bắt đầu lắng nghe). Mỗi người tán tỉnh sẽ nhận được một luồng dữ liệu hoàn toàn mới, từ đầu đến cuối. Ví dụ trên chính là một cold flow điển hình đấy.
Hot Flow: Trái lại, đây là một em người yêu "năng động" hơn. Em ấy luôn luôn hoạt động, "thả thính" không ngừng nghỉ, bất kể có ai nghe hay không. Nếu bạn đến sau, bạn sẽ chỉ nhận được dữ liệu từ thời điểm bạn bắt đầu lắng nghe thôi. Hot flow cực kỳ phù hợp cho những thứ như thông báo sự kiện, dữ liệu cập nhật theo thời gian thực...
Tóm lại, nếu bạn muốn làm việc với dữ liệu bất đồng bộ một cách thông minh, nhanh nhạy, không cần chờ đợi một cách vô vọng, thì đã đến lúc làm quen với Kotlin Flow rồi đấy. Nó sẽ giúp bạn viết code "sạch" hơn, dễ đọc hơn và quan trọng là, mang lại trải nghiệm người dùng mượt mà hơn rất nhiều!
Nếu các bạn thấy hay thì cho mình xin 1 upvote nhé, phần sau mình sẽ giới thiệu chi tiết hơn về Cold Flow và Hot Flow. Cảm ơn các bạn đã xem đến đây!