Pattern to access kotlin DataStore values without using suspend functions

I am using a class to store and load data from a DataStore in my kotlin multiplatform app. The class is simple:

class AuthPersistence(
    private val context: Context
) {
    private val Context.dataStore by preferencesDataStore(context.packageName)

    actual suspend fun saveUsername(username: String) {
        context.dataStore.edit {
            it[usernameKey] = username
        }
    }

    actual suspend fun getUsername(): String? {
        return context.dataStore.data.firstOrNull()?.get(usernameKey)
    }

    private val usernameKey = stringPreferencesKey("user_name")
}

The class AuthPersistence is a singleton that I inject using koin. My problem is, that I don’t know the best/cleanest way to make the username accessible without calling the suspend function. (Accessing the username directly from a class without launching a coroutine is less complex and the username should rarely change)

My approach would possible be to create an immutable variable that get’s changed everytime I save a new username. But as mentioned; I’m not sure if this is the kotlin way.

1

You usually want to defer collecting Flows to the UI.

In a layered app your Data Store is located at the lowest layer, the data layer. context.dataStore.data provides you with the stored values in the form of a Flow<Preferences>. You collect that flow right there by calling firstOrNull(). Collecting flows require a coroutine so you had to make getUsername a suspend function.

Instead of collecting the flow on the data layer you should pass the flow through to the layers, up into the UI. You can apply transformations to the flow on the way, if you like. In your case you would want to apply mapLatest:

fun getUsername(): Flow<String?> {
    return context.dataStore.data
        .mapLatest { it[usernameKey] }
}

That transforms the Flow<Preferences> into a Flow<String?> containing just the user name. The flow will always be up-to-date: Whenever the value is changed in the data store the flow will emit a new value.

Since you do not collect the flow you also don’t need the suspend modifier anymore.

Now, the easiest way to present this flow to the UI is by converting it to a StateFlow. That is a specially configured flow that always has a value, much like a variable. Unlike a variable it can be easily observed for changes, because it is still a flow: You just need to collect it. To convert the Flow<String?> to a StateFlow<String?> you need to apply stateIn. If you use a view model that will be the best location:

val userName: StateFlow<String?> = authPersistence.getUsername()
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = null,
    )

You need to supply a CoroutineScope here because the StateFlow will (internally) always immediately collect new values, even when there aren’t any subscribers collecting the StateFlow itself. The scope should be cancelled when not needed any more. The Android ViewModel already supplies an appropriate scope out-of-the-box, the viewModelScope.

To mitigate that the StateFlow might run the upstream flow even when there is noone collecting the StateFlow this behavior can be fine-tuned. With providing SharingStarted.WhileSubscribed(5_000) for the second parameter the StateFlow starts collecting the upstream flow as soon as the first subscriber of the StateFlow appears. It will stop collecting only 5 seconds after the last subscriber unsubscribed. The 5 seconds is an arbitrary value, high enough to allow subscribers to briefly unsubscribe and resubscribe whithout the StateFlow interrupting the upstream flow.

The last parameter provides the initial value of the flow, until the upstream flow emits its first value. This way the StateFlow will always have a value, independent of the delays of the upstream flows.

This StateFlow can now be handed over to the UI. Since the StateFlow always has a value you don’t need a suspend function or a CoroutineScope to access it:

userName.value

That will retrieve the current value. But if you are interested in observing the changes too then you need to collect the StateFlow which, again, requires a coroutine. If you use Compose for your UI there is a ready-to-use function that handles everything you need:

val userName: String? by viewModel.userName.collectAsState()

(Use collectAsStateWithLifecycle() instead if you want it to be lifecycle-aware. You’ll need the gradle dependency androidx.lifecycle:lifecycle-runtime-compose for it.)

collectAsState internally obtains an appropriate CoroutineScope and converts the flow into a Compose State. The by keyword is a Kotlin feature (Delegation) which unwraps the State and allows you to directly acces its value – without losing the State mechanics.

That’s why your composables can now simply access userName as a String?. Whenever the data store provides a new value a new recomposition is triggered with the updated userName and your UI will always be up-to-date.

If you don’t use Compose you simply need to collect the StateFlow and trigger an update to your UI from its lambda:

viewModel.userName.collect { userName ->
    // update UI with new userName
}

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật