Proto DataStore is a library for storage purposes introduced by Android Jetpack. Using this, we can store medium-sized data like the recent searched locations list.
We can use SharedPreferences for the same purpose, but it is not the main thread-safe and storing a list using it is not a good option.
Moreover, Kotlin coroutines and Flow make data storing, using Proto Datastore, asynchronous and consistent. It also provides transaction support. A Flutter developer specializes in creating Android applications using the Flutter framework.
Add below code in build.gradle file which resides in the app folder and sync the project afterward.
plugins {
id "com.google.protobuf" version "0.8.17"
}
dependencies {
/*************************************************************
* Proto Data Store
*************************************************************/implementation "androidx.datastore:datastore-core:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
/*************************************************************
* Preference Data Store(Excluding this might cause some errors.)
*************************************************************/implementation "androidx.datastore:datastore-preferences:1.0.0"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.14.0"
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
You have to define your data structure in a .proto file.
Here, Location is a model data class that contains information about a location. In this 1,2,3,4 are just unique numbers known as Field Numbers. Each variable should be assigned a unique field number. Try to allocate these field numbers between 1 to 15, as it takes only 1-byte memory. Field Number larger than 15 will take 2 bytes of memory space, so try to avoid it as long as possible.
Recent Locations is the class that contains the list of Location objects.
Provide your package name in the java_package parameter. All the generated classes will be stored in the package name provided by you.
Build the project when you are done changing the proto file.
For more information on proto buffer, check out this link.
syntax = "proto3";
option java_package = "com.kotlin.mvvm.structure.datastore";//Replace it with your package.
option java_multiple_files = true;
// This is the Location class
message Location{
int32 id = 1; // Unique ID number for this location.
string name = 2;
double longitude=3;
double latitude=4;
}
// This is object which will contain the list of recent locations
message RecentLocations {
repeated Location location = 1;
}
You need to create a serializer for your proto class which will be used by Proto DataStore internally. This is boilerplate code which will be repeated every time you implement a Proto DataStore so just copy it and replace the class name with your class name.
Here, RecentLocationsSerializer is created for serializing the RecentLocations class.
After defining the serializer, create the Proto DataStore using the “by dataStore” property delegate as shown in the code.
Now, provide this DataStore object using Hilt. For this demo, I have supplied this as a constructor parameter of DefaultDataRepostiory class.
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
/**
* Some other code…
*/
/**
* For Proto Data Store
*/ @Suppress("BlockingMethodInNonBlockingContext")
object RecentLocationsSerializer : Serializer<RecentLocations> {
override val defaultValue: RecentLocations = RecentLocations.getDefaultInstance()
override suspend fun readFrom(input: InputStream): RecentLocations {
try {
return RecentLocations.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
} catch (e: java.io.IOException) {
e.printStackTrace()
throw e
}
}
override suspend fun writeTo(t: RecentLocations, output: OutputStream) = t.writeTo(output)
}
private val Context.recentLocationsDataStore: DataStore<RecentLocations> by dataStore(
fileName = "RecentLocations.pb",
serializer = RecentLocationsSerializer
)
@Provides
@Reusable
fun provideProtoDataStore(@ApplicationContext context: Context) =
context.recentLocationsDataStore
@Provides
@Reusable
internal fun providesDataRepository(
@ApplicationContext context: Context,
recentLocationsDataStore: DataStore<RecentLocations>
): DataRepository {
return DefaultDataRepository(
context,
recentLocationsDataStore
) as DataRepository
}
}
Making Repository Interface helps in unit testing as we can create a Fake Repository easily.
interface DataRepository {
/**
* This method is used for the purpose of fetching the recent locations items from the data store.
*/ suspend fun getRecentLocations(): LiveData<List<Location>>
/**
* This method is used for the purpose of adding the recent locations items in the data store.
*/ suspend fun addRecentLocations(locations: List<Location>)
/**
* This method is used for the purpose of clearing the recent locations items.
*/ suspend fun clearAllRecentLocations()
}
Here is the implementation for the repository interface we created before.
To read data, dataStoreObject.data can be used as shown in getRecentLocations() implementation.
To add, update or clear data, we can use dataStoreObject.updateData which will provide us the DataStore class instance which we can manipulate as per our need. You can check out addRecentLocations() and clearAllRecentLocations() method implementations for knowing the usage of dataStoreObject.updateData method.
class DefaultDataRepository @Inject constructor(
private val context: Context,
private val recentLocationsDataStore: DataStore<RecentLocations>
) : DataRepository {
override suspend fun getRecentLocations(): LiveData<List<Location>> {
return recentLocationsDataStore.data.asLiveData()
.switchMap { MutableLiveData(it.locationList) }
}
override suspend fun addRecentLocations(locations: List<Location>) {
recentLocationsDataStore.updateData { recentLocations: RecentLocations ->
recentLocations.toBuilder().addAllLocation(locations).build()
}
}
override suspend fun clearAllRecentLocations() {
recentLocationsDataStore.updateData {
it.toBuilder().clear().build()
}
}
}
For showing the list of recent locations, create a list or recycler view adapter and raw layout.
Finally, we will integrate all the created classes, repositories and adapters in the view model.
@HiltViewModel
class ProtoViewModel @Inject constructor(
private val dataRepository: DataRepository
) :ViewModel() {
//Initializer
init {
//Getting all recent locations
getAllRecentLocations()
}
/**
* Some other code…
*/
//Private variables
private lateinit var _recentLocations: LiveData<List<Location>>
//Live data variables
val recentLocations: LiveData<List<Location>> = _recentLocations
private fun getAllRecentLocations() = viewModelScope.launch {
_recentLocations = dataRepository.getRecentLocations()
}
fun addRecentLocations(locations:List<Location>) = viewModelScope.launch {
dataRepository.addRecentLocations(locations)
}
fun clearRecentLocations() = viewModelScope.launch {
dataRepository.clearAllRecentLocations()
}
}
Now, use the view model instance in your activity/fragment to perform DataStore operations.
@AndroidEntryPoint
class FragmentProtoDataStoreDemo : Fragment(R.layout.fragment_proto_data_store_demo) {
/**
* Proto View Model instance.
*/ private val protoViewModel: ProtoViewModel by viewModels()
/**
* Variables for storing fake location data.
*/ private var lastId = 0
private var lastLatitude = 0.0
private var lastLongitude = 0.0
/**
* Some other code…
*/
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
…
binding.fragmentProtoDataStorePlusFab.setOnClickListener {
protoViewModel.addRecentLocations(
listOf(
Location.newBuilder()
.setId(++lastId)
.setName("Location Number : $lastId")
.setLatitude(++lastLatitude)
.setLongitude(++lastLongitude)
.build()
)
)
}
binding.fragmentProtoDataStoreClearFab.setOnClickListener{
protoViewModel.clearRecentLocations()
}
protoViewModel.recentLocations.observe(viewLifecycleOwner) {
recentLocationsListAdapter.submitList(it.toMutableList())
}
}
}
On the click of the Add Floating Action button, a new location will be added and reflected.
On the click of the Clear Floating Action Button, all locations will be deleted and reflected.
If you are following google code labs or any other sources then you might have added only two dependencies in the project build.gradle file.
implementation “androidx.datastore:datastore-core:1.0.0”
implementation “com.google.protobuf:protobuf-javalite:3.18.0”
This might cause an “Unresolved reference: dataStore” issue as shown in the below image.
This can be solved easily by adding the following dependency. Though it is the dependency for the preference data store, it solves the issue perfectly as shown in the below picture.
implementation “androidx.datastore:datastore-preferences:1.0.0”
Proto DataStore is a great way to store small to mid-range structured data in Kotlin Android Project. It can be implemented pretty fast and doesn’t require much change. Moreover, It provides a better user experience due to main-thread safety.
If you are making an application and there is a need to store structured information, then you should consider using Proto DataStore.
Proto DataStore is used for storing small to medium size data. It is one of the alternatives for SharedPreference provided by Google.
No. If you are already using SharedPreference in your application, and it works perfectly with it, then there is no need to implement this. Moreover, this only works for Kotlin projects.
Suppose you have created an application without any database. Now, you need to store a list of some items. Using a database is not the best decision in this case as it will take too much time and effort. However, you have to change its version with each change.
You can surely go for a database if there is a large set of data or multiple lists that you need to store.
When there is key-value pair data, then using preference DataStore will be a better choice. For example, storing the username of the user.
You can use Proto DataStore when there is data that cannot be stored using primitive data types.
Diving deep into SwiftUI This blog post drops us into…
Corporate efficiency and customization are vital in today's fast-paced world,…
Flutter Codemagic CI/CD makes your Flutter app build, test, and…
This website uses cookies.