Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
I'm trying to update my Android views using some data that I've got on a Firebase Realtime DB. Since I'm using Kotlin, I've encountered some problems doing this simple thing. I'm not an expert on Realtime DB, I've just discovered that the calls to the DB are made asynchronously, and this means I've got to use the data inside the onDataChange function.
I also discovered that Kotlin doesn't let me change the value of a View that I passed as a parameter to some function (like the one I created to update that View using the data retrieved from the DB). So I'm counting on you for this.
What's the best way to retrieve data from Firebase and use that data to update my Views? I would like to have some clean code, like different functions that do the right thing, without having a pile of code in the onCreate method of my MainActivity.
Maybe something along these lines:
fun fetchFeed(): LiveData<List<Feed>> {
val result: MutableLiveData<List<Feed>> = MutableLiveData()
firestore.collection("feeds")
.addSnapshotListener { collectionSnapshot, exception ->
if (exception != null) {
//...handle error
return#addSnapshotListener
}
if (collectionSnapshot == null || collectionSnapshot.isEmpty) {
//...no data or no collection
return#addSnapshotListener
}
val value: MutableList<Feed> = arrayListOf()
collectionSnapshot.documents.map {
value.add(it.toObject(Feed::class.java))
}
data.postValue(value)
}
return result
}
class FeedFragmentViewModel: ViewModel() {
//ApiService is a singleton or an DAO eg... where your fetchFeed is declared or have access to it
val result: LiveData<List<Feed>> = ApiService.fetchFeed()
}
class FeedFragment: Fragment() {
override onActivityCreated() {
//initialize view model -> in docs
viewModel.result.observe(this, Observer {
myListAdapter.updateList(it)
})
}
}
It is not a complete solution but along these lines I think you should be able to incorporate realtimedb in it. Long story short you want to observe live data which represents the data from realtime db. When ever those c hange your callback in observer gets called where you can update the UI accordingly
Related
In my ViewModel I have a few LiveData objects like this one:
val invitations = MutableLiveData(listOf<Session>())
I have set up multiple Firestore Snapshot Listeners that should update my LiveData. Therefore I give the function that instantiates the listener a lambda like this:
repository.getInvitations {
invitations.value = it
Log.d("ONLINE", "Receiving Invitations.")
}
Here is the repository function:
override fun getInvitations(onDataUpdate: (List<Session>) -> Unit) {
val invitationsQuery = db.collection("invitations").whereEqualTo("player_id", auth.currentUser?.uid)
val listener = invitationsQuery.addSnapshotListener{ querySnapshot, exception ->
if (exception != null){
Log.w("ONLINE", "Error handling Snapshot",exception)
return#addSnapshotListener
}
val sessions = mutableListOf<Session>()
querySnapshot?.documents?.forEach{
sessions.add(it.toSession())
}
onDataUpdate(sessions)
}
listeners.add(listener)
}
In my Composable I observe the LiveData with observeAsState() from the viewModel
val invitations = viewModel.invitations.observeAsState()
In my UI I use the received state as usual with invitation.value.
That worked quite fine for a while. Before I had another normal firestore query running in the init block of my viewModel that updated some other LiveData. But since I did not needed that function anymore and deleted it, my UI is not updating anymore.
My instant suggestion is that the lambda callback in the snapshot listener is not updating my liveData correctly. Which I more or less proved wrong by adding a Text element under the rest of my UI where I also use the same state. That text somehow can detect the change in the LiveData.
My other UI code is quite complex but here is quick overview:
#Composable
fun Screen(viewModel: MyviewModel = koinViewModel()){
val invitations = viewModel.invitations.observeAsState()
MyUIElement(invitations.value)
Text("${invitations.value}")
}
If more of the UI code is needed I can give it.
I also tried asking ChatGPT about this. It mentioned that snapshot listeners are running in background threads and the LiveData can only detect data changes in the Main thread. But it did not seem to be helpful to just add a runBlocking(Dispatchers.Main) {} to update it in the Main thread.
Also this cant really be the problem for my UI to not update, because my Text is showing all my changes to the firestore data if I update it in the online console.
This was bothering me for a while now. I really dont want to refactor all my Code now. A quick fix would be appreciatd. :)
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 months ago.
Improve this question
I was always facing this memory leak warning without paying much attention to it, but now I'm looking into how to deal with it and, as I know I shouldn't use WeakReference and that sort of "tricks" to avoid it, come to what I think could be a possible and simple solution.
My idea is as follows:
I have a singleton class (object) which holds all my app configuration, where I initialize a context from the Application class like this:
object AppSettings {
lateinit var context: Context
fun init(appContext: Context){
this.context = appContext
}
/* OTHER STUFF */
}
typealias aw = AppSettings
#HiltAndroidApp
class AWApplication : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
AppSettings.init(this)
}
/* OTHER STUFF */
}
I initialize that context not only in ApplicationClass, but in every activity OnCreate (which inherits from BaseActivity):
#AndroidEntryPoint
open class BaseActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppSettings.init(this)
}
}
And finally, I can access context wherever it is needed as follows:
object RandomObject {
fun DoWhatever() {
PlayAFreddieMercurySong(aw.context,)
}
}
Well, this is my possible solution and I would like to know Android gurus from SO opinion about it.
Maybe I'm leaking memory in my App Settings -where I had initially store context-, but Android Studio is not complaining about it, so I am not sure.
In the end, I'm trying to avoid passing context as a method parameter in every place it is needed for code simplicity.
"other stuff" are common between all activities and they need just ApplicationContext, then why you don't use application context in AppSettings. and thats it. BTW your solution will not leak, if and only if you call your AppSettings.init(this) in all activities.
and you don't guarantee that .
in other words "the code doesn't leak now but may be in the future" - vulnerable
if you have functions thats related to activities, fragments or any
class you want, you can use extension functions for that
you should create a kotlin file with name for ex ActivityExt. and write all of your cases that you need for activities . if you need functions for fragments you should also create another kotlin file with name FragmentExt..etc
Because database fetches usually happen asynchronously by default, a variable that holds the data from the firebase database fetch will be null when used right after the fetch. To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries. People also call the succeeding code from within 'addOnSuccessListener{}' but this seems to go against the purpose of MVVM, since 'addOnSuccessListener{}' will be called in the model part of MVVM, and the succeeding code that uses the fetched data will be in the ViewModel. The answer I'm looking for is maybe a listener or observer that is activated when the variable (whose value is filled from the fetched data) is given a value.
Edit:
by "succeeding code" I mean what happens after the database fetch using the fetched data.
As #FrankvanPuffelen already mentioned in his comment, that's what the listener does. When the operation for reading the data completes the listener fires. That means you know if you got the data or the operation was rejected by the Firebase servers due to improper security rules.
To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries.
It doesn't. Using ".await()" is indeed an asynchronous programming technique that can help us prevent our applications from blocking. When it comes to the MVVM architecture pattern, the operation for reading the data should be done in the repository class. Since reading the data is an asynchronous operation, we need to create a suspend function. Assuming that we want to read documents that exist in a collection called "products", the following function is needed:
suspend fun getProductsFirestore(): List<Product> {
var products = listOf<Product>()
try {
products = productsRef.get().await().documents.mapNotNull { snapShot ->
snapShot.toObject(Product::class.java)
}
} catch (e: Exception) {
Log.d("TAG", e.message!!)
}
return products
}
This method can be called from within the ViewModel class:
val productsLiveData = liveData(Dispatchers.IO) {
emit(repository.getProductsFromFirestore())
}
So it can be observed in activity/fragment class:
private fun getProducts() {
viewModel.producsLiveData.observe(this, {
print(it)
//Do what you need to do with the product list
})
}
I have even written an article in which I have explained four ways in which you can read the data from Cloud Firestore:
How to read data from Cloud Firestore using get()?
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
My android app communicate with back end service through REST API . I want to mock out this API to quickly develop the front end. I am using android volley as client side networking library..
I would store mock data as json in my assets folder and create my data from these json files:
fun getJsonFromAssets(context: Context, jsonPath: String): String? {
return try {
context.assets.open(jsonPath).bufferedReader().use{
it.readText()
}
} catch (e: IOException) {
null
}
}
and then get the object like (here for example for a list):
val list = gson.fromJson<List<MyObject>>(jsonString, object : TypeToken<List<MyObject>>() {}.type)
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
This post was edited and submitted for review 8 months ago and failed to reopen the post:
Original close reason(s) were not resolved
Improve this question
Please explain a use cases and pros and cons of each approach.
Use of interface.
fun doSomethingWithCallback(callback: Callback) {
// Do something
callback.call()
}
Use of high-order function.
fun doSomethingWithCallback(callback: () -> Unit) {
// Do something
callback()
}
Option 1
With option 1 you're not able to call it passing a lambda. For example this does not compile:
doSomethingWithCallback1 { print("helloWorld") }
Interestingly if the same method were defined in Java:
void doSomethingWithJavaCallback(JavaCallback callback) {
// Do something
callback.call();
}
Then you can call it using a lambda from Kotlin. This is because Kotlin only does SAM-conversion for functions defined in Java.
Option 2
In contrast if you go with option 2 you do get to call it using a lambda. And it will work both when calling it from Kotlin and from Java.
Option 3
As mentioned in the comments a third option is using a type alias like this:
typealias Callback = () -> Unit
fun doSomethingWithCallback5(callback: Callback) {
// Do something
callback()
}
You get to keep the type in the function signature and use lambdas on the call site.
You can use with a lambda :
doSomethingWithCallback { // do whatever you want }
I usually use lambda function by this one:
var doSomething: ((Any) -> Unit)? = null
and invoke callback:
doSomething?.invoke(any)
finally as same as listener:
youClass.doSomething = { any ->
// this is callback
}