How can I extract logic to viewModel? - android

In my fragment I have next code:
etAuthorName.doAfterTextChanged {
viewModel.quoteAuthor.value = it?.toString()
}
viewModel.quoteAuthor.observe(this, Observer<String> {
if (etAuthorName.text.toString() != it) {
etAuthorName.setText(it)
}
})
How i can extract this logic:if (etAuthorName.text.toString() != it) to my viewModel?

You can use databinding in this case, learn more here: https://developer.android.com/topic/libraries/data-binding
Edit: You can try something like this:
In your ViewModel class:
#get:Bindable
var bindetAuthorName: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.bindetAuthorName)
}
get() = field
fun comparison(){
if(bindetAuthorName != quoteAuthor){
bindetAuthorName = quoteAuthor
}
}
in your xml edittext/ textview:
<Edittext
.....
android:text="#={viewModel.bindetAuthorName}"/>
and whenever your quoteAuthor value is updating you can call comparison(). I hope this helps!

You can use Transformations.distinctUntilChanged add this inside viewmodel and listen inside your fragment instead observer
quoteAuthor
val authorDistinct = Transformations.distinctUntilChanged(quoteAuthor)

In order to really move your logic to ViewModel
You can change it to this
Fragment
etAuthorName.doAfterTextChanged {
viewModel.triggerQuoteAuthor(etAuthorName.text.toString(), it?.toString())
}
viewModel.quoteAuthor.observe(this, Observer<String> {
etAuthorName.setText(it)
})
ViewModel
fun triggerQuoteAuthor(beforeText: String, afterText: String) {
if (beforeText != afterText) {
quoteAuthor.value = afterText
}
}

Related

Jetpack compose lazy column not recomposing with list

I have a list which is stored inside a Viewmodel via Stateflow.
class FirstSettingViewModel : ViewModel() {
private val _mRoomList = MutableStateFlow<List<InitRoom>>(mutableListOf())
val mRoomList: StateFlow<List<InitRoom>> = _mRoomList
...
I observe the flow via collectAsState(). The LazyColumn consists of Boxes which can be clicked.
val roomList = mViewModel.mRoomList.collectAsState()
Dialog {
...
LazyColumn(...) {
items(roomList.value, key = { room -> room.room_seq}) { room ->
Box(Modifier.clickable {
**mViewModel.selectItem(room)**
}) {...}
}
}
}
When a click event occurs, the viewModel changes the 'isSelected' value via a copied list like this.
fun selectItem(room: InitRoom) = viewModelScope.launch(Dispatchers.IO) {
try {
val cpy = mutableListOf<InitRoom>()
mRoomList.value.forEach {
cpy.add(it.copy())
}
cpy.forEach {
it.isSelected = it.room_seq == room.room_seq
}
_mRoomList.emit(cpy)
} catch (e: Exception) {
ErrorController.showError(e)
}
}
When in an xml based view and a ListAdapter, this code will work well, but in the above compose code, it doesn't seem to recompose the LazyColumn at all. What can I do to re-compose the LazyColumn?
Use a SnapshotStateList instead of an ordinary List
change this,
private val _mRoomList = MutableStateFlow<List<InitRoom>>(mutableListOf())
val mRoomList: StateFlow<List<InitRoom>> = _mRoomList
to this
private val _mRoomList = MutableStateFlow<SnapshotStateList<InitRoom>>(mutableStateListOf())
val mRoomList: StateFlow<SnapshotStateList<InitRoom>> = _mRoomList

StateFlow collect does not change the data correctly

I'm using MVI architecture, coroutine and flow,
when I receive the data from the API, the status changed from LOADING to SUCCESS, and when I collect the stateFlow variable it submit the recyclerView successfully while when I try to hide the loading view (progressBar, lottie) the view freeze for a moment and it does not disappear.
I tried two ways
I tried to use the stateFlow in XML like this: android:visibility="#{viewModel.writersFlow.status == Results.Status.LOADING ? View.VISIBLE : View.GONE}", and of course I put lifecycleOwner = viewLifecycleOwner in onViewCreated function and I passed the viewModel to the XML
change the the visibility programmatically like bellow:
Repository:
override suspend fun getWriters(): Flow<Results<BaseModel<WriterModel>?>> =
resultFlowData(
networkCall = {
remoteDataSource.getResult {
endpoints.getWriters()
}
}
)
ViewModel:
private val _writersFlow: MutableStateFlow<Results<BaseModel<WriterModel>?>> =
MutableStateFlow(start())
val writersFlow: StateFlow<Results<BaseModel<WriterModel>?>>
get() = _writersFlow.asStateFlow()
private fun fetchWriters() {
viewModelScope.launch(Dispatchers.IO) {
writerRepository.getWriters().collect {
"writers: $it".log()
_writersFlow.emit(it)
}
}
}
Fragment: here in fragment you will see the binding.loading.visibility = View.GONE in both cases (SUCCESS, ERROR)
private fun gettingWriters() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.writersFlow.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect {
when (it.status) {
Results.Status.SUCCESS -> {
writersAdapter.submitList(it.data?.response)
binding.loading.visibility = View.GONE
}
Results.Status.ERROR -> {
"$HOME_FRAGMENT: ${it.status}, ${it.code}, ${it.message}".log()
if (!it.message.isNullOrEmpty()) binding.root.snack(it.message ?: "") {}
binding.loading.visibility = View.GONE
}
Results.Status.LOADING -> {
binding.loading.visibility = View.VISIBLE
}
else -> {}
}
}
}
}
In the first way the loading view does not even appear but in the second way it appears but never disappear.

second item of list is not showing

this is my viewModel code:
val workout = state.get<Workout>("workout")
var steps : List<Step> = emptyList()
fun getSteps() = viewModelScope.launch {
steps = workout?.let { stepDao.getWorkoutSteps(it.workoutId) }!!
Log.e("KEK", steps.size.toString())
}
and the fragment:
viewModel.getSteps()
if (viewModel.steps.isNotEmpty()) {
binding.apply {
currentStepName.text = viewModel.steps[0].name
currentStepDuration.text = viewModel.steps[0].length.toString()
nextStep.text = "${viewModel.steps[1].name} : ${viewModel.steps[1].length.toString()}"
}
}
this part where i fill the nextStep text doesn't work, a second item in the list exists, but the text doesn't get shown `
thank you
When you call viewModel.getSteps() it starts a coroutine, so you need to wait for it to finish. but you don't wait and try to update the UI using viewModel.steps which may not have been updated yet, because coroutine may not have started.
in your case you can use a LiveData object to fix the issue.
In your ViewModel
val liveSteps: MutableLiveData<List<Step>> = MutableLiveData()
fun getSteps() = viewModelScope.launch {
steps = workout?.let { stepDao.getWorkoutSteps(it.workoutId) }!!
liveSteps.postValue(steps)
Log.e("KEK", steps.size.toString())
}
Now observe this LiveData in Fragment and update UI
viewModel.liveSteps.observe(viewLifeCycleOwner, androidx.lifecycle.observe{
if(!(it.isNullOrEmpty()) && it.size == 2){
binding.apply {
currentStepName.text = it[0].name
currentStepDuration.text = it[0].length.toString()
nextStep.text = "${it[1].name} : ${it[1].length.toString()}"
}
}
})

Operations With Android LiveData in EditText?

I have 3 editext and I have to multiply the numbers written in the first two, and make the result appear live on the third using Android Livedata.
viewModel.num1.observe(this,
Observer { newNum1-> binding.ediText1.text = newNum1.toString() }
viewModel.num2.observe(this,
Observer { newNum2-> binding.ediText2.text = newNum2.toString() }
viewModel.num3.observe(this,
Observer { newNum3-> binding.ediText3.text = newNum3.toString() }
I tried something like this, with 2 MutableLiveData and one MediatorLivedata, but i did something wrong because it didn't update live the third EditText. Could someone help me?
class MyViewModel : ViewModel() {
private var num1 = MutableLiveData<Int>();
private var num2 = MutableLiveData<Double>();
private var mun3 = MediatorLiveData<Double>();
num3.addSource(num1, this::onChanged);
num3.addSource(num2, this::onChanged);
private fun onChanged(x : Double) {
var a = num1.value
var b = num2.value
if (a== null)
a= 0;
if (b== null)
b= 0.0;
num3.setValue(a * b);
}
}
I'm using Kotlin but i accept any kind of code, even in java.
Thank you for your patience and help!
Best regards, Mark.
Please try something like that, but be aware of nullability. One of received values can be null
fun <A, B> LiveData<A>.combineWith(b: LiveData<B>): LiveData<Pair<A?, B?>> =
MediatorLiveData<Pair<A?, B?>>().apply {
var lastA: A? = this#combineWith.value
var lastB: B? = b.value
addSource(this#combineWith) {
lastA = it
value = Pair(lastA, lastB)
}
addSource(b) {
lastB = it
value = Pair(lastA, lastB)
}
}
viewModel.num1
.combineWith(viewModel.num2)
.observe(
this,
Observer { (first, second) ->
if (first != null && second != null) {
someEditText.text = (first * second).toString()
}
}
)
That might not be your literal code, because it's not compileable. You can't pass the same function to be used as the observer for either source, since the source types are different.
Your onChanged() function doesn't use the input parameter, so you can remove that and call it from a lambda you pass to each addSource call.
You can also simplify the content of your function by using Elvis operators.
private val num1 = MutableLiveData<Int>()
private val num2 = MutableLiveData<Double>()
private val num3 = MediatorLiveData<Double>().apply {
addSource(num1) { onChanged() }
addSource(num2) { onChanged() }
}
private fun onChanged() {
val a = num1.value ?: 0
val b = num2.value ?: 0.0
num3.value = a * b
}

Changing a textView based on the value of a variable using coroutines in kotlin

I have a boolean variable isConnected. I want to change a textView on the basis of this variable.
For example
if (isConnected):
textView text = a
else
textView text = b
This code should be running throughout the program. I tried to implement this in Android Studio but the app was unable to load anything.
var isConnected = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setStatusBar()
}
private fun setStatusBar() {
CoroutineScope(Main).launch {
while(true){
checkConnection()
}
}
}
#SuppressLint("SetTextI18n")
private fun checkConnection() {
CoroutineScope(Main).launch {
if(!isConnected){
status.text = "Disconnected"
}
else{
status.text = "Connected"
}
}
}
As I change the value of isConnected, I want the app to change the text present in the status textview,
Can someone tell me why my code is not working?
It is not a good practice too use infinite loops, Use Mutable LiveData to easily achieve this thing. You have to create a MutableLiveData variable isConnected of type Boolean and observer it value for changes to modify the text accordingly.
Variable declaration:
private val isConnected:MutableLiveData<Boolean> = MutableLiveData(false)
Now in onCreate observe it for changes:
isConnected.observe(this,Observer {
newValue ->
if(!newValue){
status.text = "Disconnected"
}
else{
status.text = "Connected"
}
})
Now to set the value use the below syntax:
isConnected.postValue(true)

Categories

Resources