I'm trying to implement the new typed DataStore API in Java and I'm having some issues. All the documentation seems to be in Kotlin only and trying to create a new data store is not as straight forward from the Java side it seems.
Calling DataStoreFactoryKt.createDataStore() from Java requires me to provide all the arguments including the ones with default values in the Kotlin implementation. There doesnt seem to be any #JvmOverloads annotation for that function, resulting in my predicament.
fun <T> Context.createDataStore(
fileName: String,
serializer: Serializer<T>,
corruptionHandler: ReplaceFileCorruptionHandler<T>? = null,
migrations: List<DataMigration<T>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): DataStore<T> =
DataStoreFactory.create(
produceFile = { File(this.filesDir, "datastore/$fileName") },
serializer = serializer,
corruptionHandler = corruptionHandler,
migrations = migrations,
scope = scope
)
What's the better way around this, if there is any? Or is the Data Store api simple designed to be used with Kotlin only? I have no idea how I would go about providing a CoroutineScope argument from Java.
After updating dataStore dependency to '1.0.0-alpha08' as below.
// DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha08"
You can have preferences implementation as follow:
private val Context.dataStore by preferencesDataStore("app_preferences")
After that if you like create some preference key:
private object Keys {
val HIDE_VISITED = booleanPreferencesKey("hide_visited")
}
other options can be stringPreferencesKey, intPreferencesKey, etc.
Saving value example:
context.dataStore.edit { prefs -> prefs[Keys.HIDE_VISITED] = hideVisited }
Reading saved value example:
val hideVisited = preferences[Keys.HIDE_VISITED] ?: false
You need to add to your Grade build file the dependency for DataStore preferences:
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha04"
and not the one for Types, that way you will be able to resolve the androidx.datastore.preferences.Context.createDataStore method that you are expecting:
public fun Context.createDataStore(
name: String,
corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
migrations: List<DataMigration<Preferences>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): DataStore<Preferences> =
PreferenceDataStoreFactory.create(
corruptionHandler = corruptionHandler,
migrations = migrations,
scope = scope
) {
File(this.filesDir, "datastore/$name.preferences_pb")
}
If you need to use proto dataStore from version 1.0.0-beta01, you can:
implementation("androidx.datastore:datastore-core:1.0.0-beta01")
initialize with Data Store Factory
val data: DataStore<SomeMessage> = DataStoreFactory.create(
serializer = SessionSerializer, // your Serializer
corruptionHandler = null,
migrations = emptyList(),
scope = CoroutineScope(Dispatchers.IO + Job())
And continue as before
data.updateData {
it.toBuilder().setAddress("address").build()
}
data.collect { ChargingSession ->
ChargingSession.address
}
This is only valid for dependency version 1.0.0-alpha08 and above
#Ercan approach is correct but requires context every time we need access to the dataStore.
Below is a better approach for the same.
private val Context._dataStore: DataStore<Preferences> by preferencesDataStore(APP_PREFERENCES)
private val dataStore : DataStore<Preferences> = context._dataStore
companion object {
const val APP_PREFERENCES = "app_preferences"
}
reference: https://issuetracker.google.com/issues/173726702
Related
I've got some configuration in a json file stored at the asset folder of the app.
I need this configuration during my complete app so I thought a CompositionLocalProvider may be a good choice.
But now I realize I need the context for parsing that json file and that doesn't seem to be possible.
Might there be another way to achieve the goal I'm looking for?
This is my implementation so far:
val LocalAppConfiguration = compositionLocalOf {
Configuration.init(LocalContext.current) // <-- not possible
}
Where my Configuration is like:
object Configuration {
lateinit var branding: Branding
fun init(context: Context) {
val gson = GsonBuilder().create()
branding = gson.fromJson(
InputStreamReader(context.assets.open("branding.json")),
Branding::class.java
)
}
}
I would be very grateful if someone could help me further
compositionLocalOf is not a Composable function. So, LocalContext.current can not be used.
I believe you can achieve the similar goal if you move the initialization of branding outside of the default factory. You'd then do the initialization inside your actual composable where you have access to Context.
Here's a sample code to explain what I'm talking about.
val LocalAppConfiguration = compositionLocalOf {
Configuration
}
#Composable
fun RootApp(
isDarkTheme: Boolean = isSystemInDarkTheme(),
content: #Composable () -> Unit
) {
val brandedConfiguration = Configuration.init(LocalContext.current)
MaterialTheme {
CompositionLocalProvider(LocalAppConfiguration provides brandedConfiguration) {
//your app screen composable here.
}
}
}
Note that you will also have to modify your init method slightly.
object Configuration {
lateinit var branding: Branding
fun init(context: Context) : Configuration {
val gson = GsonBuilder().create()
branding = gson.fromJson(
InputStreamReader(context.assets.open("branding.json")),
Branding::class.java
)
return this
}
}
So with the new alpha07 version, Android ditched the private val dataStore = context.createDataStore(name = "settings_pref"), however the new way they use datastore doesn't work for me.
Since upgrading from "androidx.datastore:datastore-core:1.0.0-alpha06" to alpha07, I can't seem to make my datastore syntax work without getting red-colored code (the error comes when i add context.dataStore.edit). Also downgrading back to alpha06, code that previously worked is now not working anymore (with createDataStore).
What I'm using is their example on the main page but going anywhere else they still haven't updated their examples besides this one.
#Singleton
class PreferencesManager #Inject constructor(#ApplicationContext context: Context) {
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
suspend fun incrementCounter() {
context.dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
}
}
If someone knows the problem (or my error), I would appreciate it.
This was throwing me too, but I figured it out (aka, guessed until it worked):
// Note: This is at the top level of the file, outside of any classes.
private val Context.dataStore by preferencesDataStore("user_preferences")
class UserPreferencesManager(context: Context) {
private val dataStore = context.dataStore
// ...
}
This is for a DataStore<Preferences>, but if you need a custom serializer, you can do the following (same parameters as the old method):
// Still top level!
private val Context.dataStore by dataStore(
fileName = "user_preferences",
serializer = MyCustomSerializer,
)
I had the same problem and found a mistake: Context should be as a member of class to use it in any method:
private val Context.dataStore by preferencesDataStore("preferences")
class Preferenses(val context: Context) { get, set, etc}
I switched from SharedPreferences to Jetpack DataStore.
I am unable to mock the data from the datastore call in the Instrumentation test
In declaration
val dataStore = context.createDataStore(name = "App Name")
In the declaration get a string from preferences
suspend fun getString(
key: Preferences.Key<String>,
defaultVal: String,
context: Context
): String {
return dataStore?.data?.catch {
if (it is IOException) {
it.printStackTrace()
emit(emptyPreferences())
} else {
throw it
}
}?.map {
it[key] ?: defaultVal
}?.first() ?: ""
}
In the usage
SharedPreferenceHelper.getString(
Preferences.Key<T>,
"",
requireContext()
)
Manipulate datastore preferences string key to get mocked value in instrumentation test as the desired value.
Thank you in advance.
createDataStore returns DataStore<Preference>, this will help you to create mock of Class with generic type
Or you can use mockito-kotlin to create mock like this in kotlin
val mockDataStore = mock<DataStore<Preference>>()
var wall= ArrayList<VKWall>()
try {
val response = r.getString("response") as String
val moshi = Moshi.Builder().build()
val type: Type = Types.newParameterizedType(
ArrayList::class.java,
VKWall::class.java
)
val jsonAdapter: JsonAdapter<ArrayList<VKWall>> = moshi.adapter(type)
wall = jsonAdapter.fromJson(response)!!
} catch (e: JSONException){}
return wall
It can not create adapter. Debuger can`t execute this string and goes to exception of function over this code
val jsonAdapter: JsonAdapter<ArrayList<VKWall>> = moshi.adapter(type)
I`am trying to do everything like there
https://github.com/square/moshi
Platform java.util.ArrayList<com.e.app.fragments.vk_tabs.WallFragment.DataPackage.VKWall> (with no annotations) requires explicit JsonAdapter to be registered
#Parcelize
#JsonClass(
generateAdapter = true)
data class VKWall (
// val UserName:String="",
// val UserSurname:String="",
#Json(name = "text")
val Text:String="" ,
// val attachments: Attachments?,
// val copyright: String="",
// val repost: Repost?
):Parcelable
{
}
The problem is in that moshi don't have adapter for yours class VKWall. To resolve this you could add KotlinJsonAdapterFactory that based on reflection:
val moshi = Moshi.Builder()
// ... add your own JsonAdapters and factories ...
.add(KotlinJsonAdapterFactory())
.build()
Or you could use generated adapter like this:
// Annotate yours class #JsonClass(generateAdapter = true)
#JsonClass(generateAdapter = true)
class VKWall(
....
)
More documentation about this https://github.com/square/moshi#kotlin
More about yours problem https://www.zacsweers.dev/a-closer-look-at-moshi-1-9/
Now, for Kotlin classes, you either need to use code gen,
KotlinJsonAdapterFactory, or supply your own custom JsonAdapter.
This is a potentially dangerous change! In projects using code gen,
there are cases where Kotlin classes could have (appeared to) Just
Work™️ before if you forgot to annotate them with #JsonClass. These
will fail at runtime now. If you're worried about this, I suggest
using Moshi 1.9 only in debug builds for a period of time to tease
these out before releasing production builds with it.
I understand the need for creating getter and setter points for LiveData in the ViewModel, but I'm looking to understand how the get() syntax works in Android.
ie:
val isRealtime: LiveData<Boolean>
get() = _isRealtime
private val _isRealtime = MutableLiveData<Boolean>()
get() is not related to Android.
val isRealtime: LiveData<Boolean>
get() = _isRealtime
Here, get() is overriding the automatically-generated Kotlin getter function for the isRealtime property. So, instead of returning its own value, it returns the value of _isRealtime.
Personally, I recommend simpler syntax:
private val _isRealtime = MutableLiveData<Boolean>()
val isRealtime: LiveData<Boolean> = _isRealtime
The objective of either of these is to keep the mutability private, so consumers of this class do not accidentally update the MutableLiveData themselves.
In Kotlin we have multiple ways of exposing live data from ViewModel to the view.
class MyViewModel: ViewModel() {
// Solution 1 - make MutableLiveData public
// This approach works, but this is a bad idea because
// view can modify the LiveData values
val liveDataA1 = MutableLiveData<State>()
// Solution 2 - let's make LiveData public (expose it instead of MutableLiveData)
// Now from view perspective this solution looks fine, bu we have a problem,
// because we need MutableLiveData within ViewModel to put/post new values to
// the stream (we can't post values to LiveData).
val liveDataA2 = MutableLiveData<State>() as LiveData<State>
// Let's capture our requirements:
// 1. We need to expose (immutable) LiveData to the view,
// so it cannot edit the data itself.
// 2. We need to access MutableLiveData from ViewModel to put/post new values.
// Now, let's consider few appropriate solutions
// Solution 3
// Let's name mutable live data using underscore prefix
private val _liveData3 = MutableLiveData<State>()
val liveData3 = _liveData3 as LiveData<State>
// Solution 4
// We can also perform casting by specifying type for a variable
// (we can do it because MutableLiveData extends LiveData)
private val _liveData4 = MutableLiveData<State>()
val liveData4: LiveData<State> = _liveData4
// Solution 5
// Starting from Kotlin 1.4-M.2 we can delegate call to another property
private val _liveData5 = MutableLiveData<State>()
val liveData5 by this::_liveData5
// Solution 6
// These above solutions work quite well, but we could do even better by
// defining custom asLiveData extension function.
private val _liveData6 = MutableLiveData<State>()
val liveData6 = _liveData6.asLiveData()
fun <T> MutableLiveData<T>.asLiveData() = this as LiveData<T>
// Amount of code is similar, but notice that this approach works much better
// with code completion.
// Solution 7 (IMO Best)
// We can also use alternative naming convention - use "mutableLiveData"
// as variable for mutable live data instead of using underscore prefix
private val mutableLiveData7 = MutableLiveData<State>()
val liveData7 = mutableLiveData7.asLiveData()
// BTW
// We could also expose getLiveData8() method, but liveData is a state not an action.
// Solution 9
// This does not create backing field for the property
// (more optimised but still Solution 7 is easier to use)
private val _liveData9 = MutableLiveData<State>()
val liveData9 get() = _liveData9 as LiveData<State>
}
I wrote a util function for this logic:
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import kotlin.reflect.KProperty
fun <T> immutable(data: MutableLiveData<T>): Immutable<T> {
return Immutable(data)
}
class Immutable<T>(private val data: MutableLiveData<T>) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): LiveData<T> {
return data
}
}
Then you can use in any of your ViewModel as:
private val _counter: MutableLiveData<Int> = MutableLiveData()
val counter: LiveData<Int> by immutable(_counter)
or in short:
private val _counter = MutableLiveData<Int>()
val counter by immutable(_counter)