How to retrieve data from Firestore that stored as an Array and set them as EditText values in Kotlin? - kotlin

I have stored some data as an array in Firestore using the following code. Now, I want to get those values and put them one by one into the EditTexts. How can I do that?
private fun addZipToFirebase() {
val zipList = createListOfZipCodes()
mFireStore.collection(Constants.USERS)
.document(FirestoreClass().getCurrentUserID())
.update("zip_codes", zipList)
.addOnSuccessListener {
Toast.makeText(
this#AssignZIPCodeActivity,
"Zip Codes updates successfully",
Toast.LENGTH_SHORT
).show()
}
.addOnFailureListener { exception ->
Log.e(
javaClass.simpleName,
exception.message,
exception
)
}
}
Edit:
I am trying with the following code to get the data. I want each Zip Code under the field name zip_codes (in the screenshot), in each EditText (etPinCodeOne, etPinCodeTwo, etPinCodeThree and so on). But with following code what I am getting is all the zip codes together in the EditText. Exctely like, [123456, 789456, 132645,798654, 798654, 799865, 764997, 497646, 946529, 946585]. I want each codes in each EditText.
private fun getZipCodesFromFirebase() {
mFireStore.collection(Constants.USERS)
.document(FirestoreClass().getCurrentUserID())
.get()
.addOnSuccessListener { document ->
val list: ArrayList<String> = ArrayList()
list.add(document["zip_codes"].toString())
Toast.makeText(this#AssignZIPCodeActivity,list.toString(),Toast.LENGTH_SHORT).show()
binding.etZipCodeOne.setText(list[0])
}
}
Can someone help me with this please?

To be able to get the zip_codes array, you need to have inside your User class, a property called zip_codes that needs to be declared of type List:
val zip_codes: List<String>
Now, to get it accordingly, please use the following lines of code:
val uid = FirebaseAuth.getInstance().currentUser!!.uid
val rootRef = FirebaseFirestore.getInstance()
val usersRef = rootRef.collection("users")
val uidRef = usersRef.document(uid)
uidRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val document = task.result
if (document.exists()) {
val zip_codes = document.toObject(User::class.java).zip_codes
//Do what you need to do with your list
} else {
Log.d(TAG, "No such document")
}
} else {
Log.d(TAG, "get failed with ", task.exception)
}
}
Since you are getting multiple zip codes, you should consider using a ListView, or even better a RecyclerView, rather than EditTexts.

Related

Combine search and sort with kotlin flow

I need to search and sort data simultaneously. I did it for search but it wont trigger for sort. I'm also using pagination.
User can type in searchView and flow will trigger, but problem is when i change sortState (ascending or descending) it wont trigger flow for searching articles on api endpoint.
ViewModel:
private val currentQuery = MutableStateFlow(DEFAULT_QUERY)
private val sortState = MutableStateFlow<SortOrderState>(SortOrderState.Ascending)
val flow = currentQuery
.debounce(2300)
.filter {
it.trim().isNotEmpty()
}
.distinctUntilChanged()
.flatMapLatest { query ->
articleRepository.getSearchResult(query.lowercase(Locale.ROOT),sortState.value)
}
Fragment:
lifecycleScope.launch {
viewModel.flow.collectLatest { articles ->
binding.recyclerViewTop.layoutManager = LinearLayoutManager(context)
binding.recyclerViewTop.adapter = adapter.withLoadStateHeaderAndFooter(
header = ArticleLoadStateAdapter { adapter.retry() },
footer = ArticleLoadStateAdapter { adapter.retry() }
)
adapter.submitData(articles)
}
}
In fragment I have function: viewModel.searchNews(newText)
And in Main activity: viewModel.setSortState(SortOrderState.Ascending) (one menu item clicked) to see if MutableStateFlow.value is changed. I can see that in ViewModel i can change these values but if I do:
val flow=currentQuery.combine(sortState){
query,state ->
}
I never changes if I click on sort menu item, only if I type something to search.
Edit: sortState is not updating in flow variable, I checked setSortState and I can clearly see that state is changed but in flow I only send ascending all the time.
Main activity:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_sortAsc -> {
viewModel.setSortState(SortOrderState.Ascending)
}
R.id.menu_sortDesc -> {
viewModel.setSortState(SortOrderState.Descening)
}
}
return super.onOptionsItemSelected(item)
}
ViewModel:
fun setSortState(sortOrderState: SortOrderState) {
sortState.value = sortOrderState
}
SortOrderState:
sealed interface SortOrderState{
object Ascending : SortOrderState
object Descening : SortOrderState
}
Edit 2: Collecting in HomeFragment it always gives me Ascending value even if i click on menu item for descending sort
lifecycleScope.launch {
viewModel.sortState.collectLatest {
Log.d(TAG, "onCreateViewSort: $it")
}
In ViewModel I can see sortState is changed:
fun setSortState(sortOrderState: SortOrderState) {
sortState.value = sortOrderState
Log.d(TAG, "setSortState: ${sortState.value}")
}
You aren't using your sort state as a Flow. You're only passively using its value, so your output flow won't automatically update when the value changes.
Instead, you need to combine your flows.
Here, I also moved your lowercase transformation before the distinctUntilChanged because I think that makes more logical sense. Also, it makes sense to include the trim in the transformation and not just in the filter.
val flow = currentQuery
.debounce(2300)
.map { it.trim().lowercase(Locale.ROOT) }
.filter { it.isNotEmpty() }
.distinctUntilChanged()
.combine(sortState) { query, sort -> query to sort }
.flatMapLatest { (query, sort) ->
articleRepository.getSearchResult(query, sort)
}
You might also consider tagging this with shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 1) so the search doesn't have to restart on a screen rotation.

Rerun StateFlow When Filter Needs to Change?

I have a StateFlow containing a simple list of strings. I want to be able to filter that list of Strings. Whenever the filter gets updated, I want to push out a new update to the StateFlow.
class ResultsViewModel(
private val api: API
) : ViewModel() {
var filter: String = ""
val results: StateFlow<List<String>> = api.resultFlow()
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
}
It's easy enough to stick a map onto api.resultFlow():
val results: StateFlow<List<String>> = api.resultFlow()
.map { result ->
val filtered = mutableListOf<String>()
for (r in result) {
if (r.contains(filter)) {
filtered.add(r)
}
}
filtered
}
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
But how do I get the flow to actually emit an update when filter changes? Right now, this only works with whatever the initial value of filter is set to.
You could have the filter update another StateFlow that is combined with the other one. By the way, there's filter function that is easier to use than manually creating another list and iterating to get your results.
class ResultsViewModel(
private val api: API
) : ViewModel() {
private val filterFlow = MutableStateFlow("")
var filter: String
get() = filterFlow.value
set(value) {
filterFlow.value = value
}
val results: StateFlow<List<String>> =
api.resultFlow()
.combine(filterFlow) { list, filter ->
list.filter { it.contains(filter) }
}
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
}

How to invoke function based on condition of iterated value of Mono<List<String>> without using subscribe()?

I want to invoke a function that will notify the admin about some information missing, but I do not want to subscribe to this Mono, because I will subscribe to it later. The problem is I have some log which is called inside doOnSuccess() and when I use subscribe() and then build a response where I zip listOfWords value, the same log is logged twice and I do not want a code to behave that way.
Is there any way to retrieve that value in checkCondition() in a way that will not invoke doOnSuccess() or should I use some other function in merge() that can replace doOnSuccess()?
Should I use subscribe() only once on given Mono or is it allowed to use it multiple times?
Thank you in advance!
The functions are called in the presented order.
Code where log is called:
private fun merge(list1: Mono<List<String>>, list2: Mono<List<String>>) =
Flux.merge(
list1.flatMapMany { Flux.fromIterable(it) },
list2.flatMapMany { Flux.fromIterable(it) }
)
.collectList()
.doOnSuccess { LOG.debug("List of words: $it") }
Code where subscribe is called:
private fun checkCondition(
listOfWords: Mono<List<String>>,
) {
listOfWords.subscribe {
it.forEach { word ->
if (someCondition(word)) {
alarmSystem.notify("Something is missing for word {0}")
}
}
}
}
Code where response is built:
private fun buildResponse(
map: Mono<Map<String, String>>,
list1: List<SomeObject>,
listOfWords: Mono<List<String>>
): Mono<List<Answer>> {
val response = Mono.zip(map, Mono.just(list1), listOfWords)
.map { tuple ->
run {
val tupleMap = tuple.t1
val list = tuple.t2
val words = tuple.t3
list
.filter { someCondition(words) }
.map { obj -> NewObject(x,y) }
}
}

TornadoFx pass data from mysql to table view

I have a class to pass notes from mysql to tableview in kotlin but i cant seem to make it work
Im a little new in kotlin for desktop, only used in android with firebase
This is my class to get the notes
class Notes(id_notes: Int = 0, title: String = "none", description: String = "none"){
private var id_notes: SimpleIntegerProperty = SimpleIntegerProperty(id_notes)
private var title: SimpleStringProperty = SimpleStringProperty(title)
private var description: SimpleStringProperty = SimpleStringProperty(description)
fun getId(): Int {
return id_notes.get()
}
fun setId(id: Int) {
id_notes.set(id)
}
fun getTitle(): String {
return title.get()
}
fun setTitle(Title: String) {
title.set(Title)
}
fun getDescription(): String {
return description.get()
}
fun setDescription(Description: String) {
description.set(Description)
}
then i have the actual code
tableview(data){
prefWidth = 400.0
column("ID", Notes::getId)
column("Title", Notes::getTitle)
rowExpander {
label {
this.text = Notes::getDescription.toString()
}
}
}
private fun getNotes(){
try {
val notes = Notes()
val sql = ("SELECT id_notes, title, description, date FROM notes")
val con: Connection? = Conn.connection()
stmt = con?.createStatement()
rs = stmt?.executeQuery(sql)
while (rs!!.next()) {
notes.setId(rs!!.getInt("id_notes"))
notes.setDescription(rs!!.getString("description"))
notes.setTitle(rs!!.getString("title"))
data.add(notes.toString())
}
} catch (ex: SQLException) {
alert(Alert.AlertType.ERROR, "Error", "Could not perform this action")
}
}
At the end I will try to solve your problem, but please, read this part first, because this is far more import for you than the actual answer. I believe your programing skills (for now) are not the required for the kind of things you are trying to accomplish, especially because you are converting your class to string before adding it to your data (which seem to be a collection of string not a collection of Notes), so I don’t know how you expect the tableview will get your Id, Title and Description.
Also, you have a constructor for Notes, but you are overcomplicating things by not using it and assign values later. In other hand, you getNotes() function is never call in your code, probably is called in some other part you are not showing.
Because of that, I think you should slow down a little bit, try to level up your basic skills (specially working with classes and collections), them read the tornadofx manual, and them try with this kind of stuff.
Now this is my solution. First try this without the database. I did it this way because I don’t know if there is any problem with your database. Them change the getNotes() function to the way is in your code, without converting the notes.toString(), just de data.add(notes). Remember to click the button to load the data.
class Prueba: View("MainView") {
//data should be an FXCollections.observableArrayList<Notes>
//You didn't show your data variable type, but apparently is some collection of string
val data = FXCollections.observableArrayList<Notes>()
override val root = vbox {
tableview(data){
prefWidth = 400.0
column("ID", Notes::getId)
column("Title", Notes::getTitle)
rowExpander() {
label() {
//Note the difference here, Notes::getDescription.toString() won't do what you want
this.text = it.getDescription()
}
}
}
//This button is calling the function getNotes(), so data can be loaded
button("Load Data") {
action {
getNotes()
}
}
}
//Note this function is out side root now
private fun getNotes() {
data.clear()
data.add(Notes(1,"Title 1", "Description 1"))
data.add(Notes(2,"Title 2", "Description 2"))
data.add(Notes(3,"Title 3", "Description 3"))
}
}

RxJava Filter on Error

This question is loosely related to this question, but there were no answers. The answer from Bob Dalgleish is close, but doesn't support the potential error coming from a Single (which I think that OP actually wanted as well).
I'm basically looking for a way to "filter on error" - but don't think this exists when the lookup is RX based. I am trying to take a list of values, run them through a lookup, and skip any result that returns a lookup failure (throwable). I'm having trouble figuring out how to accomplish this in a reactive fashion.
I've tried various forms of error handling operators combined with mapping. Filter only works for raw values - or at least I couldn't figure out how to use it to support what I'd like to do.
In my use case, I iterate a list of IDs, requesting data for each from a remote service. If the service returns 404, then the item doesn't exist anymore. I should remove non-existing items from the local database and continue processing IDs. The stream should return the list of looked up values.
Here is a loose example. How do I write getStream() so that canFilterOnError passes?
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import org.junit.Test
class SkipExceptionTest {
private val data: Map<Int, String> = mapOf(
Pair(1, "one"),
Pair(2, "two"),
Pair(4, "four"),
Pair(5, "five")
)
#Test
fun canFilterOnError() {
getStream(listOf(1, 2, 3, 4, 5))
.subscribeOn(Schedulers.trampoline())
.observeOn(Schedulers.trampoline())
.test()
.assertComplete()
.assertNoErrors()
.assertValueCount(1)
.assertValue {
it == listOf(
"one", "two", "four", "five"
)
}
}
fun getStream(list: List<Int>): Single<List<String>> {
// for each item in the list
// get it's value via getValue()
// if a call to getValue() results in a NotFoundException, skip that value and continue
// mutate the results using mutate()
TODO("not implemented")
}
fun getValue(id: Int): Single<String> {
return Single.fromCallable {
val value: String? = data[id]
if (value != null) {
data[id]
} else {
throw NotFoundException("dat with id $id does not exist")
}
}
}
class NotFoundException(message: String) : Exception(message)
}
First .materialize(), then .filter() on non-error events, then .dematerialize():
getStream(/* ... */)
.materialize()
.filter(notification -> { return !notification.isOnError(); })
.dematerialize()
I ended up mapping getValue() to Optional<String>, then calling onErrorResumeNext() on that and either returning Single.error() or Single.just(Optional.empty()). From there, the main stream could filter out the empty Optional.
private fun getStream(list: List<Int>): Single<List<String>> {
return Observable.fromIterable(list)
.flatMapSingle {
getValue(it)
.map {
Optional.of(it)
}
.onErrorResumeNext {
when (it) {
is NotFoundException -> Single.just(Optional.empty())
else -> Single.error(it)
}
}
}
.filter { it.isPresent }
.map { it.get() }
.toList()
}