Updated- The code in the question works now
I'm trying to run a function after clicking on a button. The function updates an array and I want to run the next lines (The next lines transfer me to another activity) if the array isn't empty.
I tried to open the new activity within the filterPlaces function but with no success, startActivity and Intent don't work.
This is the function that updates the array:
var places = ArrayList<Place>() //Global place array
class MainActivity : AppCompatActivity() {
fun filterPlaces(types: ArrayList<String>, foods: ArrayList<String>, maxPrice: Int, maxProximity: Int) {
var typesList = types
val foodList = foods
if (types.isEmpty()) {
typesList = arrayListOf("Restaurant", "Hangouts")
if (foods.isEmpty()) {
foodList.add("Pizza")
}
}
val db = FirebaseFirestore.getInstance()
db.collection("places").get().addOnSuccessListener { result ->
for (document in result) {
val typeMatches = document.data["Type"].toString() in typesList
val foodMatches = document.data["Food"].toString() in foodList
var price = 0
when (document.data["Price"].toString()) {
"Very cheap" -> price = 0
"Cheap" -> price = 1
"Average" -> price = 2
"Far" -> price = 3
"Very far" -> price = 4
}
val priceMatches = price <= maxPrice
var proximity = 0
when (document.data["Proximity"].toString()) {
"Very close" -> proximity = 0
"Close" -> proximity = 1
"Far" -> proximity = 2
"Very far" -> proximity = 3
}
val proximityMatches = proximity <= maxProximity
if (typeMatches and foodMatches and priceMatches and proximityMatches) {
val place = Place(
document.data["Place"].toString(),
document.data["Type"].toString(),
document.data["Food"].toString(),
document.data["Price"].toString(),
document.data["Proximity"].toString()
)
places.add(place)
Log.d("name", "Place added successfully")
}
}
//Openning the results activity
if (places.isNotEmpty()) {
val i = Intent(this, RelevantPlaces::class.java)
val b = Bundle()
b.putParcelableArrayList("places", places)
i.putExtra("bundle", b)
startActivity(i)
}
}
.addOnFailureListener { exception ->
Log.d("name", "Error getting documents.")
}
}
This is the on click function:
fun onSortFilterClicked(view: View) {
if (places.isEmpty()) filterPlaces(types, foods, priceRange.progress, proximityRange.progress)
}
I want to run filterPlaces first, update the places array while I run it, and only than check if the array is still empty and if not open the new activity.
What actually happens is that it calls the filterPlaces but doesn't do it, instead it checks the places array (The if condition in the code) and only than goes into filterPlaces and run what's in it, resulted in me need to press twice on the button and only than the array has values.
I'm running this on Android Studio and I'm new at this Kotlin world and android developing in general.
Is there a solution for this? Either open the activity within the function or make the function run first?
What is happening?
An asynchronous request is made in filterPlaces, this is why the method itself returns immediately and passes control to the next code block.
How to fix this?
Move your code starting another Activity into the scope of your success listener. A better approach is to place this code into a separate method and just call it as needed.
Placed the filterPlace function outside the Main Activity class. Moved the function in the Main Activity class and it worked.
Related
I have two classes: one is the viewModel (ShoesViewMode.ktl) to keep the data and the other is the Fragment to show the data.(ShoesList.kt )
ShoesList has a mutableList of words and I recover it from the ShoesList to show in a scrollview.
I get a new word from an EditText from a Fragment -> Click on Save button -> Pass this word through nave Args to ShoesDetails -> save it in the ShoesViewModel -> Recover it and show in the Fragment.
The problem is that every time I add a new word, the list doesn't keep the last one added. It's like if the mutableList was always recreated.
I would like to go back the screen and add a new word, and a new word and see the previous words added in the list.
How can I keep the words added previously?
ShoesViewModel.kt
class ShoesViewModel(_newShoe: String?=null): ViewModel() {
private var _shoesList = MutableLiveData<MutableList<String>>()
init {
//receives the score when the class is instanciated
_shoesList.value = mutableListOf(
"trade",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll"
)
}
val shoesList: LiveData<MutableList<String>>
get() = _shoesList
fun save (newShoe: String){
_shoesList.value?.add(newShoe)
}
ShoesList. kt // FRAGMENT to show data
val shoesListArgs by navArgs()
viewModelFactory = ShoeViewModelFactory(shoesListArgs.newShoe)
viewModel = ViewModelProvider(this, viewModelFactory).get(ShoesViewModel::class.java)
//get the view Model //pass to the variable in the xml
binding.shoesViewModel = viewModel
binding.setLifecycleOwner(this)
viewModel.save(shoesListArgs.newShoe) //save new Shoe to the List
//keeps track of shoesList. This is an OBSERVER
viewModel.shoesList.observe(viewLifecycleOwner, Observer{ shoesList ->
loadShoes(shoesList)
})
//actig to floating button
binding.buttonFloating.setOnClickListener{ view:View ->
view.findNavController().navigate(ShoesListDirections.actionShoesListToShoesDetails())
}
return binding.root
}
private fun loadShoes(list:MutableList<String>){
for(shoe in list){
val newTextViewShoe = TextView(context)
newTextViewShoe.text = shoe // add TextView to LinearLayout
binding.linearlayoutShoelist.addView(newTextViewShoe)
}
}
}
I save a new word, the Fragment changes and list shows the new word. When I go back to the screen to save a new word, it saves the new word, but the previous on disappears.
In method save You need:
fun save(newShoe: String) {
if (shoeList.value.isNullOrEmpty){
shoeList.value = mutableListOf(newShoe)
}
else {
shoesList.value = shoesList.value.add(newShoe)
}
}
Your problem is that You are trying to set data to the list rather than livedata by calling livedata.value.add(). Your value here is getValue() method, that does nothing but gives you value. If You need to update a value in livedata, then You go:
liveData.value = newValue
Whether this means setValue() method. Additionally, if You want to set data from another thread than main, use postValue():
liveData.postValue(newValue)
In my application I want update data with SharedFlow and my application architecture is MVI .
I write below code, but just update one of data!
I have 2 spinners and this spinners data fill in viewmodel.
ViewModel code :
class MyViewModel #Inject constructor(private val repository: DetailRepository) : ViewModel() {
private val _state = MutableStateFlow<MyState>(MyState.Idle)
val state: StateFlow<MyState> get() = _state
fun handleIntent(intent: MyIntent) {
when (intent) {
is MyIntent.CategoriesList -> fetchingCategoriesList()
is MyIntent.PriorityList -> fetchingPrioritiesList()
}
}
private fun fetchingCategoriesList() {
val data = mutableListOf(Car, Animal, Color, Food)
_state.value = DetailState.CategoriesData(data)
}
private fun fetchingPrioritiesList() {
val data = mutableListOf(Low, Normal, High)
_state.value = DetailState.PriorityData(data)
}
}
With below codes I filled spinners in fragment :
lifecycleScope.launch {
//Send
viewModel.handleIntent(MyIntent.CategoriesList)
viewModel.handleIntent(MyIntent.PriorityList)
//Get
viewModel.state.collect { state ->
when (state) {
is DetailState.Idle -> {}
is DetailState.CategoriesData -> {
categoriesList.addAll(state.categoriesData)
categorySpinner.setupListWithAdapter(state.categoriesData) { itItem ->
category = itItem
}
Log.e("DetailLog","1")
}
is DetailState.PriorityData -> {
prioritiesList.addAll(state.prioritiesData)
prioritySpinner.setupListWithAdapter(state.prioritiesData) { itItem ->
priority = itItem
}
Log.e("DetailLog","2")
}
}
When run application not show me number 1 in logcat, just show number 2.
Not call this line : is DetailState.CategoriesData
But when comment this line viewModel.handleIntent(MyIntent.PriorityList) show me number 1 in logcat!
Why when use this code viewModel.handleIntent(MyIntent.CategoriesList) viewModel.handleIntent(MyIntent.PriorityList) not show number 1 and 2 in logcat ?
The problem is that a StateFlow is conflated, meaning if you rapidly change its value faster than collectors can collect it, old values are dropped without ever being collected. Therefore, StateFlow is not suited for an event-like system like this. After all, it’s in the name that it is for states rather than events.
It’s hard to suggest an alternative because your current code looks like you shouldn’t be using Flows at all. You could simply call a function that synchronously returns data that you use synchronously. I don’t know if your current code is a stepping stone towards something more complicated that really would be suitable for flows.
I am new to jetpack compose,
I am showing a dataset to Lazycolumn that works fine.
When I try to fliter i.e. replace the original dataset with different dataset,
my Lazycolumn initially shows it the replaced one but in a flash it goes back to original dataset again.
Here some snippets to what I have done, I suspect my compose logic and could not able to find out
// The main composeable where I am observing the changes and calling ShowList to populate
#Composeable
fun SomeScreen(viewModel : TestviewModel){
val stateCountryCodeMap = remember { mutableStateOf(mapOf<String?, List<CountryInfo>>()) }
// observe and retrieve the dataset.
testViewModel.stateMapCountryInfo.collectAsState().value.let {
stateCountryCodeMap.value = it
}
// Some Test to buttom to load a different data set
someRandomeButtom.click{
viewModel. filterCountryList()
}
// request to load original data set
testViewModel.fetchCountryList()
ShowList(
currentSelected = stateCountryCodeSelectedIndex.value,
groupedCountryInfo = stateCountryCodeMap.value,
testViewModel = testViewModel
)
}
// The ShowList function to display
#Composable
private fun ShowList(
currentSelected: Pair<String, Int>,
groupedCountryInfo: Map<String?, List<CountryInfo>>,
testViewModel: TestViewModel
) {
// LazyColumn stuff to render itmems from map dataset
}
// and TestviewModel
val stateMapCountryInfo = MutableStateFlow(mapOf<String?, List<CountryInfo>>())
val stateSortedCountryInfo = MutableStateFlow(listOf<CountryInfo>())
fun fetchCountryList() {
// some IO operation which gives result
when (val result = getCountryListRepo.invoke()) {
is Result.Success -> {
val countryInfoResultList = result.data
// sort the list by country name and store
stateSortedCountryInfo.value = countryInfoResultList.sortedBy { it.countryName }
// save it to map
stateMapCountryInfo.value = stateSortedCountryInfo.value.groupBy { it.countryName?.get(Constants.ZERO).toString() }
}
}
val stateFilteredCountryInfo = MutableStateFlow(listOf<CountryInfo>())
fun filterCountryList() {
// some IO operation
// filter on the sorted array, // results 2 items - India, Indonesia
val filteredList = stateSortedCountryInfo.value.filter {
it.countryName?.contains("Ind") == true
}
// store the filtered result
stateFilteredCountryInfo.value = filteredList
// now assing it to map
stateMapCountryInfo.value = stateFilteredCountryInfo.value.groupBy { it.countryName?.get(Constants.ZERO).toString() }
}
}
}
Till this point, it is a straight forward display of items in ShowList method.
Now, back to SomeScreenMethod(..),
now If I click on that random button, which gives me a different/filtered list as expected
and LazyColumn updates it but then again goes back to original state.
Can you pinpoint where it went wrong?
it seems when recomposition is happening, getting triggered multiple times.
testViewModel.fetchCountryList()
can you try this and check
LaunchedEffect(Unit) {
testViewModel.fetchCountryList()
}
In our class this week, our assignment is to create a simple cake-baking app. There are two edit text fields(wetText, dryText) in which the user can input ingredients to add to the cake. There is a mixbutton that is clicked after adding the ingredients. On the mixbutton click, my goal is to list the added ingredients from the editText into a new textView(cakeText) as such:
You added --- to the batter!
You added --- to the batter!
You added --- to the batter!
etc.
We're supposed to use a for-loop, and I think I may be on the right track by using an array. The batterList was my most recent attempt at this, so I know it's wrong, but I'd love to know how to fix it! I've been working at it for hours and have gotten close, but not close enough. I hope this makes sense. My mind isn't working right at this point. Any advice would be greatly appreciated!
val wetList = mutableListOf<String>()
val dryList = mutableListOf<String>()
val batterList = arrayOf(wetList)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun wetButtonTapped(view: View) {
wetList.add(wetText.text.toString())
wetText.text.clear()
ingredientList.text = "You have ${wetList.count()} wet ingredients \n You have ${dryList.count()} dry indredients"
}
fun dryButtonTapped(view: View) {
dryList.add(dryText.text.toString())
dryText.text.clear()
ingredientList.text = "You have ${wetList.count()} wet ingredients \n You have ${dryList.count()} dry indredients"
}
fun mixButtonTapped(view: View) {
//cakeText.text = "You added ${wetList}"
for (item in batterList){
cakeText.text = "You added $item to the batter!"
}
as far as i understand you want to show all added ingredients in single text view. so instead of declaring a new array which can be sometime unmanageable i will directly use both of array and StringBuilder class for building the whole string
fun mixButtonTapped(view: View) {
val stringBuilder = StringBuilder()
// here (wetList + dryList) will be merged into single list
for (item in (wetList + dryList))
stringBuilder.append("You added $item to the batter!\n")
cakeText.text = stringBuilder.toString()
}
so instead of managing third array i will directly use both array and dispose the merged array when my task is done in this case it will be when the loop is completed.
You're always assigning the last line of your intended list into cakeText.
Try this:
cakeText.text = "${cakeText.text}\nYou added $item to the batter!"
This should add the items one by one in the text.
Also, you may need to change batterList from val to var and reassign it on mixButtonTapped. So final code should look like this:
var batterList = arrayOf(wetList)
...
fun mixButtonTapped(view: View) {
batterList = arrayOf(wetList)
for (item in batterList){
cakeText.text = "${cakeText.text}\nYou added $item to the batter!"
}
}
I want to write a function that returns a list that contains the ids of documents.
For reading the data out of the databse (Cloud Firestore) I use an OnCompleteListener.
This is my Code so far:
private fun getInvolvedChannels(): MutableList<String> {
var involvedList = mutableListOf<String>()
val currentUserId = FirebaseAuth.getInstance().currentUser?.uid
firestoreInstance.collection("users").document(currentUserId!!).collection("engagedChatChannels").get()
.addOnCompleteListener ( object : OnCompleteListener<QuerySnapshot> {
override fun onComplete(task: Task<QuerySnapshot>) {
task.result.documents.forEach {
val currentChannelId = it.id.toString()
involvedList.add(currentChannelId)
}
}
})
involvedList = buildChatChannelString(involvedList)
return involvedList
}
The reading part already works fine. The problem is that the list gets returned before the Listener even red one Id. So it always returns an empty list. Where is my Error? How can I make sure the Listener finished before I return the List?