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
}
}
Related
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) }
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)
}
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)
}
}
Here is my viewmodel:
class MyProfileEditSharedViewModel : ViewModel() {
val question = MutableLiveData<String>()
val answer = MutableLiveData<String>()
fun setQuestion (q: String) {
question.value = q
}
fun setAnswer (a: String) {
answer.value = a
}
}
I set the data using setQuestion and setAnswer like this:
viewModel.setQuestion(currentUserInList.question)
viewModel.setAnswer(currentUserInList.answer)
I try to get question and answer from the ViewModel like this:
val qnaQuestionData = communicationViewModel.question as String
val qnaAnswerData = communicationViewModel.answer as String
Compiler says I cannot cast MutableLiveData to string.
Should I make a separate getter like my setter? I heard that you don't need to use getters and setters in kotlin, is there anyway to edit val question and val answer in my viewmodel without using getters and setters?
Thank you!!
You can't cast it to String because the type of object is MutableLiveData, but you can access the value with .value property
val qnaQuestionData = communicationViewModel.question.value
val qnaAnswerData = communicationViewModel.answer.value
in this case, may facing errors about MutableLiveData initialization.
another way is observing the LiveData for changes:
communicationViewModel.question.observe(this, Observer{ data->
...
})
Or if you have not accessed to any lifecycle owner
communicationViewModel.question.observeForever(Observer{ data->
...
})
but please remember to remove the observer through removeObserver method
for setting the values it's better to use properties directly or binding way
communicationViewModel.question.postValue("some new value")
Or
communicationViewModel.question.value = "some new value"
Suggestion for MutableLiveData properties:
val question: MutableLiveData<String> by lazy { MutableLiveData<String>() }
val answer: MutableLiveData<String> by lazy { MutableLiveData<String>() }
https://developer.android.com/reference/android/arch/lifecycle/LiveData
Create some sort of getter method in your ViewModel
fun getQuestion(): LiveData<String> {
return question //this works because MutableLiveData is a subclass of LiveData
}
Then, you can observe the value in whatever class you care about the value. ie:
communicationsViewModel.getQuestion().observe(this, Observer {
//do something with the value which is 'it'. Maybe qnaQuestionData = it
}
Note if you're trying to observe the value from a fragment or something, you will have to change the parameter this, to viewLifecycleOwner
I have a LiveData property for login form state like this
private val _authFormState = MutableLiveData<AuthFormState>(AuthFormState())
val authFormState: LiveData<AuthFormState>
get() =_authFormState
The AuthFormState data class has child data objects for each field
data class AuthFormState (
var email: FieldState = FieldState(),
var password: FieldState = FieldState()
)
and the FieldState class looks like so
data class FieldState(
var error: Int? = null,
var isValid: Boolean = false
)
When user types in some value into a field the respective FieldState object gets updated and assigned to the parent AuthFormState object
fun validateEmail(text: String) {
_authFormState.value!!.email = //validation result
}
The problem is that the authFormState observer is not notified in this case.
Is it possible to trigger the notification programically?
Maybe you can do:
fun validateEmail(text: String) {
val newO = _authFormState.value!!
newO.email = //validation result
_authFormState.setValue(newO)
}
You have to set the value to itself, like this: _authFormState.value = _authFormState.value to trigger the refresh. You could write an extension method to make this cleaner:
fun <T> MutableLiveData<T>.notifyValueModified() {
value = value
}
For such a simple data class, I would recommend immutability to avoid issues like this altogether (replaces all those vars with vals). Replace validateEmail() with something like this:
fun validateEmail(email: String) = //some modified version of email
When validating fields, you can construct a new data object and set it to the live data.
fun validateFields() = _authFormState.value?.let {
_authFormState.value = AuthFormState(
validateEmail(it.email),
validatePassword(it.password)
)
}