Show DataStore protobuf settings in Jetpack Compose - android

I'm having an issue trying to display the data saved in my DataStore on startup in Jetpack Compose.
I have a data store set using protocol buffers to serialize the data. I create the datastore
val Context.networkSettingsDataStore: DataStore<NetworkSettings> by dataStore(
fileName = "network_settings.pb",
serializer = NetworkSettingsSerializer
)
and turn it into a livedata object in the view model
val networkSettingsLive = dataStore.data.catch { e ->
if (e is IOException) { // 2
emit(NetworkSettings.getDefaultInstance())
} else {
throw e
}
}.asLiveData()
Then in my #Composable I try observing this data asState
#Composable
fun mycomposable(viewModel: MyViewModel) {
val networkSettings by viewModel.networkSettingsLive.observeAsState(initial = NetworkSettings.getDefaultInstance())
val address by remember { mutableStateOf(networkSettings.address) }
Text(text = address)
}
I've confirmed that the data is in the datastore, and saving properly. I've put some print statements in the composible and the data from the datastore makes it, eventually, but never actually displays in my view. I want to say I'm not properly setting my data as Stateful the right way, but I think it could also be not reading from the data store the right way.
Is there a display the data from the datastore in the composable, while displaying the initial data on start up as well as live changes?

I've figured it out.
What I had to do is define the state variables in the composable, and later set them via a state controlled variable in the view model, then set that variable with what's in the dataStore sometime after initilization.
class MyActivity(): Activity {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
MainScope.launch {
val networkSettings = viewModel.networkSettingsFlow.firstOrNull()
if (networkSettings != null) {
viewModel.mutableNetworkSettings.value = networkSettings
}
}
}
}
class MyViewModel(): ViewModel {
val networkSettingsFlow = dataStore.data
val mutableNetworkSettings = mutableStateOf(NetworkSettings.getInstance()
}
#Composable
fun NetworkSettings(viewModel: MyViewModel) {
val networkSettings by viewModel.mutableNetworkSettings
var address by remember { mutableStateOf(networkSettings.address) }
address = networkSettings.address
Text(text = address)
}

Related

does mutableStateOf function like LiveData?

I am trying to store the data in mutableStateOf() to view it in my compose function, I receive the response in my variable and assign it to State<List?> but in my compose function I recieve no data at all, yet the init block in viewmodel when I debug showes that my data is recieved?
I dont understand how MutablStates works and why it doesn't function like Live data? and if it doesn't functions like LiveData what is its alternative in JetPack compose
Here is my code in my Viewmodel
private var _categoryItems = mutableStateOf<List<Data>?>(emptyList())
val categoryItems : State<List<Data>?> = _categoryItems
init {
val service = RetrofitInstance.getRetrofitInstance().create(IGetBusinessCategory::class.java)
viewModelScope.launch {
var logText = ""
logText = service.getBusinessCategory().toString()
val categoryItems = service.getBusinessCategory().body()!!.data
Log.v("CategoryItems", logText)
_categoryItems = mutableStateOf(categoryItems)
}
}
}
and here how I try to get the data in my Compose Screen
val businessCategoryModel = BusiniessCategoryViewModel()
val listOfCategories by rememberSaveable { businessCategoryModel.categoryItems }
Yet when I debug I recieve no items at all in listOfCategories while its recieved in _categoryItems in the viewmodel, so how can I get the functionality of livedata with states in compose? is this even possiable?
Instead of:
_categoryItems = mutableStateOf(categoryItems)
Try this:
_categoryItems.value = categoryItems
Also, instead of:
val listOfCategories by rememberSaveable { businessCategoryModel.categoryItems }
Try this:
val listOfCategories by remember { mutableStateOf(businessCategoryModel.categoryItems) }

Jetpack Compose recompostion of property change in list of objects

I am quite new to Jetpack compose and have an issue that my list is not recomposing when a property of an object in the list changes. In my composable I get a list of available appointments from my view model and it is collected as a state.
// AppointmentsScreen.kt
#Composable
internal fun AppointmentScreen(
navController: NavHostController
) {
val appointmentsViewModel = hiltViewModel<AppointmentViewModel>()
val availableAppointments= appointmentsViewModel.appointmentList.collectAsState()
AppointmentContent(appointments = availableAppointments, navController = navController)
}
In my view model I get the data from a dummy repository which returns a flow.
// AppointmentViewModel.kt
private val _appointmentList = MutableStateFlow(emptyList<Appointment>())
val appointmentList : StateFlow<List<Appointment>> = _appointmentList.asStateFlow()
init {
getAppointmentsFromRepository()
}
// Get the data from the dummy repository
private fun getAppointmentsFromRepository() {
viewModelScope.launch(Dispatchers.IO) {
dummyRepository.getAllAppointments()
.distinctUntilChanged()
.collect { listOfAppointments ->
if (listOfAppointments.isNullOrEmpty()) {
Log.d(TAG, "Init: Empty Appointment List")
} else {
_appointmentList.value = listOfAppointments
}
}
}
}
// dummy function for demonstration, this is called from a UI button
fun setAllStatesToPaused() {
dummyRepository.setSatesInAllObjects(AppointmentState.Finished)
// Get the new data
getAppointmentsFromRepository()
}
Here is the data class for appointments
// Appointment data class
data class Appointment(
val uuid: String,
var state: AppointmentState = AppointmentState.NotStarted,
val title: String,
val timeStart: LocalTime,
val estimatedDuration: Duration? = null,
val timeEnd: LocalTime? = null
)
My question: If a property of one of the appointment objects (in the view models variable appointmentList) changes then there is no recomposition. I guess it is because the objects are still the same and only the properties have changed. What do I have to do that the if one of the properties changes also a recomposition of the screen is fired?
For example if you have realtime app that display stocks/shares with share prices then you will probably also have a list with stock objects and the share price updates every few seconds. The share price is a property of the stock object so this quite a similiar situation.

Jetpack Compose view doesn't observes state updates

I have a state class
object SomeState {
data class State(
val mainPhotos: List<S3Photo>? = emptyList(),
)
}
VM load data via init and updates state
class SomeViewModel() {
var viewState by mutableStateOf(SomeState.State())
private set
init {
val photos = someSource.load()
viewState = viewState.cope(mainPhotos = photos)
}
}
Composable takes data from state
#Composable
fun SomeViewFun(
state = SomeState.State
) {
HorizontalPager(
count = state .mainPhotos?.size ?: 0,
) {
//view items
}
}
The problem is that count in HorizontalPager always == 0, but in logcat and debugger i see that list.size() == 57
I have a lot of screen with arch like this and they works normaly. But on this screen view state doesn't updates and i can't understand why.
UPDATE
VM passes to Composable like this
#Composable
fun SomeDistanation() {
val viewModel: SomeViewModel = hiltViewModel()
SomeViewFun(
state = viewModel.state
)
}
Also Composable take Flow<ViewEffect> and etc, but in this question it doesn't matter, because there is no user input or side effects
UPDATE 2
The problem was in data source. All code in question work correctly. Problem closed.
object wrapping is completely redundant (no fields, no functions), you can remove it (also, change the name so it won't confuse with compose's State):
data class MyState(
val mainPhotos: List<S3Photo>? = emptyList(),
)
According to Android Developers, you need to create the state in the view model, and observe the state in the composable function - your code is a bit unclear for me so I'll just show you how I do it in my apps.
create the state in the view model:
class SomeViewModel() {
private val viewState = mutableStateOf(MyState())
// Expose as immutable so it won't be edited
fun getState(): State<MyState> = viewState
init {
val photos = someSource.load()
viewState.value = viewState.value.copy(mainPhotos = photos)
}
}
observe the state in the composable function:
#Composable
fun SomeDistanation() {
val viewModel: SomeViewModel = hiltViewModel()
val state: MyState by remember { viewModel.getState() }
SomeViewFun(state)
}
Now you'll get automatic recomposition in case the state changes.

How to get json data from url to Text Composable?

How can I fetch json from url and add it's data to a Text Composable in Jetpack Compose
Here is json file
https://jsonplaceholder.typicode.com/posts
#Composable
fun Api(){
val queue = Volley.newRequestQueue(LocalContext.current)
val url = "https://jsonplaceholder.typicode.com/posts"
val jsonObjectRequest = JsonObjectRequest(
Request.Method.GET, url,null,
{ response ->
val title = response.getString("title")
print(title)
},
{ error ->
print(error.localizedMessage)
})
queue.add(jsonObjectRequest)
}
Just get the data however you want to inside a viewmodel. Then, store it in a variable, like var data by mutableStateOf("")
Then access this variable through the viewmodel from your text Composable. Updating this variable like a normal string will trigger recompositions
EDIT BASED ON THE COMMENT BELOW:-
Although it is not necessary to store it in a viewmodel, it is the recommended best practice. You can also store the state inside your normal activity class or even the Composable using remember(not recommended for important state storage)
However, by viewmodel, I just meant,
class mViewModel: ViewModel(){
var data by mutableStateOf("")
private set //only viewmodel can modify values
fun onLoadData(){
data = //json extraction logic
}
fun onDataChange(newData: String){
data = newData
}
}
Then, in your activity,
class mActiviry: AppCompatActivity(){
val vm by viewmodels<mViewModel>() //See docs for better approaches of initialisation
//...
setContent {
Text(vm.data)
}
}
Done
Edit:-
Alternately, ditch the onDataLoad()
class mViewModel: ViewModel(){
var data by mutableStateOf("")
private set //only viewmodel can modify values
init{
data = // code from the "Api" method in your question
}
fun onDataChange(newData: String){
data = newData
}
}

How to use MutableStateFlow to save cache in android?

I am now implementing MutableStateFlow to store the cache value in Android MVVM architecture . The goal is, it could be fetched only at the first time to avoid much redundant network connection and it could be also updated when needed. Therefore, I have the following questions:
How to reset the value and request fetch data again in the MutableStateFlow with the following code?
Am I on a wrong track to use lazy to save the cache value?
class WeatherRepository {
private val mDefaultDispatcher: IDefaultDispatcher by inject()
private val scope by lazy { CoroutineScope(mDefaultDispatcher.io() + SupervisorJob()) }
val cacheWeather by lazy {
val flow = MutableStateFlow<List<Weather>?>(null)
scope.launch(mDefaultDispatcher.io()) {
val response = getWeather()
if (response.isValid) {
val data = response.data
flow.value = data
}
}
flow
}
}
class ViewModelA {
private val mRepository: WeatherRepository by inject()
val weather by lazy {
mRepository.cacheWeather.asLiveData()
}
fun requestUpdateOnWeather() {
//TODO (dunno how to make the mutableStateFlow reset and fetch data again)
}
}
class ViewModelB {
private val mRepository: WeatherRepository by inject()
val weather by lazy {
mRepository.cacheWeather.asLiveData()
}
}
Appreciate any comment or advice
Your lazy block will only initialize the cacheWeather property, when you use it.
Fetching the cached data should happen on upper level:
Use the same flow to emit from local and network data
Set constraint when to fetch from network; local data absence, time constraints etc.
Following function is just for illustration, fetches first from local storage then tries to fetch from network if the local data is not present or constraint is met.
val flow = MutableFlow<Data>(Data.empty())
fun fetchData() = coroutinesScope.launch {
val localData = getLocalData()
if(localData.isDataPresent()) {
flow.emit(localData)
}
if (willFetchFromNetwork()) {
val networkData = getNetwork()
flow.emit(networkData)
cacheData(networkData)
}
}

Categories

Resources