Firestore and Unicode - android

The documentation describes that Firestore supports Unicode. You just need to insert already formatted text into Firestore. But when unloading, the following are not taken into account:
Line break;
Unicode characters inserted directly into the text (eg \u000a).
The code is below.
Repository
suspend fun getData(): Response<List<Model>> =
suspendCoroutine { cont ->
val collection =
firestore
.collection(COLLECTION_NAME)
.whereEqualTo(DEFAULT_CONDITION_FIELD, DEFAULT_CONDITION_VALUE)
.orderBy(SORT_FIELD, SORT_DIRECTION)
.get()
collection
.addOnSuccessListener { query ->
val data = arrayListOf<Model>()
query.toObjects(ModelDomain::class.java).forEach { data.add(it.toModel()) }
cont.resume(Response.Success(data))
}
.addOnFailureListener { cont.resume(Response.Error(it)) }
}
ViewModel
private val _data: LiveData<Response<List<Model>>> = loadData()
val data get() = _data
private fun loadData(): LiveData<Response<List<Model>>> =
liveData(Dispatchers.IO) {
emit(Response.Loading)
try {
emit(repository.getData())
} catch (e: Exception) {
emit(Response.Error(e))
}
}
Model
data class ModelDomain(
var description: String = ""
) : KoinComponent {
fun toModel() =
Model(
description = description
)
}
data class Model(
val description: String
)
Part of the code has been omitted.
UPDATE
Just wrote in Notepad ++:
Copied this to Firestore:
Result:

Firestore does not, in any way, modify data that you write to it. If you write something to a document, then read the document, you will get exactly the same data that you put into it.
If you're looking at the document in the Firebase console, you will not see all carriage returns and whitespace. Those are collapsed to save space on screen when rendering large amounts of data. But if you read the data programmatically, it will definitely be exactly as you wrote it.

Related

java.util.ConcurrentModificationException while inserting data into room database

I am trying to download data from firebase firestore and insert it into the room DB for some offline use and avoid time-lag using the MVVM architecture pattern but when I do that I get an java.util.ConcurrentModificationException error I am inserting the data into the room DB inside a coroutine.
My code
class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {
private var mDatabase: AppDatabase = AppDatabase.getInstance(application)!!
private val postListRoom: MutableList<PostRoomEntity> = mutableListOf()
private val postList: LiveData<MutableList<PostRoomEntity>>? = getPostList2()
private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val db: FirebaseFirestore = FirebaseFirestore.getInstance()
private val myTAG: String = "MyTag"
#JvmName("getPostList")
fun getPostList(): LiveData<MutableList<PostRoomEntity>>? {
return postList
}
#JvmName("getPostList2")
fun getPostList2(): LiveData<MutableList<PostRoomEntity>>? {
var postsDao: PostsDao? = null
Log.d(myTAG, "postDao getPost is " + postsDao?.getPosts())
return mDatabase.postsDao()?.getPosts()
// return postList
}
fun loadDataPost() {
val list2 = mutableListOf<PostRoomEntity>()
db.collection("Posts")
.addSnapshotListener { snapshots, e ->
if (e != null) {
Log.w(myTAG, "listen:error", e)
return#addSnapshotListener
}
for (dc in snapshots!!.documentChanges) {
when (dc.type) {
DocumentChange.Type.ADDED -> {
dc.document.toObject(PostRoomEntity::class.java).let {
list2.add(it)
}
postListRoom.addAll(list2)
viewModelScope.launch(Dispatchers.IO) {
mDatabase.postsDao()?.insertPost(postListRoom)
}
// mDatabase.let { saveDataRoom(postListRoom, it) }
}
DocumentChange.Type.MODIFIED -> {
}
DocumentChange.Type.REMOVED -> {
Log.d(myTAG, "Removed city: ${dc.document.data}")
}
}
}
}
}
}
PostsDao
#Dao
interface PostsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPost(PostEntity: MutableList<PostRoomEntity>)
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllPosts(PostEntity :List<PostRoomEntity>)
#Query("Select * from PostRoomEntity")
fun getPosts(): LiveData<MutableList<PostRoomEntity>>
// #Query("SELECT * FROM notes WHERE id= :id")
// open fun getNoteById(id: Int): NoteEntity?
}
It is very error-prone to use MutableLists with asynchronous tasks or to expose them to outside functions. You are doing both, and this can result in them being modified from two different places in code simultaneously, which can cause a ConcurrentModificationException.
You should use read-only Lists to eliminate this risk. For example, use vars of type List instead of vals of type MutableList.
Some other issues with your code:
You are adding the whole contents of the list to the main list on each step of iteration, so the last item is added once, the second-to-last item is added twice, and so on. You are also inserting that whole exploded list in your local database on each step of iteration, so it is even more exponentially multiplied with redundancies. If you are just trying to update your local database with changes, you should only be inserting a single row at a time anyway.
Unnecessary nullability used in a few places. There's no reason for the DAO or your LiveData to ever be null.
Unnecessary intermediate variables that serve no purpose. Like you create a variable var postsDao: PostsDao? = null and log the null value and never use it.
Redundant and non-idiomatic getters for properties you could expose as public directly.
Redundant backing property for the value that's already held in a LiveData.
You can make your DAO functions suspend so you don't have to worry about which dispatchers you're using to call them.
There's no reason for the DAO to have an insert overload for a MutableList instead of a List. I think the parameter should just be a single item.
You can have a single coroutine iterate the list of changes instead of launching separate coroutines to handle each individual change.
I also recommend not mixing Hungarian and non-Hungarian member names. Actually I don't recommend using Hungarian naming at all, but it's a matter of preference.
And it's a little confusing that you have two databases, but there is nothing about their names to distinguish them.
Fixing these problems, your code will look like this, but there might be other issues because I can't test it or see what it's hooked up to. Also, I don't use Firebase, but I feel like there must be a more robust way of keeping your local database in sync with Firestore than trying to make individual changes with a listener.
class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {
private val localDatabase: AppDatabase = AppDatabase.getInstance(application)!!
private val mutablePostList = MutableLiveData<List<PostRoomEntity>>()
val postList: LiveData<List<PostRoomEntity>> = mutablePostList
private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val firestore: FirebaseFirestore = FirebaseFirestore.getInstance()
private val myTAG: String = "MyTag"
fun loadDataPost() {
db.collection("Posts")
.addSnapshotListener { snapshot, e ->
if (e != null) {
Log.w(myTAG, "listen:error", e)
return#addSnapshotListener
}
mutablePostList.value = snapshot!!.documents.map {
it.toObject(PostRoomEntity::class.java)
}
viewModelScope.launch {
for (dc in snapshot!!.documentChanges) {
when (dc.type) {
DocumentChange.Type.ADDED -> {
val newPost = dc.document.toObject(PostRoomEntity::class.java)
localDatabase.postsDao().insertPost(newPost)
}
DocumentChange.Type.MODIFIED -> {
}
DocumentChange.Type.REMOVED -> {
Log.d(myTAG, "Removed city: ${dc.document.data}")
}
}
}
saveDataRoom(postListRoom, localDatabase) // don't know what this does
}
}
}
}
#Dao
interface PostsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPost(postEntity: PostRoomEntity)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllPosts(postEntity: List<PostRoomEntity>)
#Query("Select * from PostRoomEntity")
fun getPosts(): LiveData<MutableList<PostRoomEntity>>
// #Query("SELECT * FROM notes WHERE id= :id")
// open fun getNoteById(id: Int): NoteEntity?
}

How to propperly exclude the Document Id property when saving a custom class to Firestore?

My custom class is designed to store the Document Id besides the relevant data.
data class Appointment(
#DocumentId
val UID: String,
val date: Timestamp,
val header: String
) {
companion object {
fun DocumentSnapshot.toAppointment() : Appointment? {
try {
val date = getTimestamp("date") ?: Timestamp(Date())
val header= getString("header").toString()
return Appointment(id, date, header)
} catch (e: Exception) {
Log.e(TAG, "Error converting appointment", e)
return null
}
}
}
}
Reading a document to my class is no problem due to my custom toAppointment function, unfortunately I can't prevent firestore to write the UID data to firestore using the add() function:
val appointment = Appointment("", Timestamp(Date()), "Header")
docRef.collection("appointments").add(appointment)
In Firestore the newly created document always have an UID-field (with an empty string).
I added the #DocumentId annotation as mentioned here - unfortunately this seems to only work with the set() function.
Thank you very much

Firebase Cloud Firestore does not return data or errors

I am learning to use Firebase Firestore and I have created a list of items that I want to display in my app. When trying to load the list, I don't receive the data but I also don't get any error. I cannot display the data in the Log. What could be happening?
fun getDriverData(): LiveData<MutableList<Driver>> {
val mutableData = MutableLiveData<MutableList<Driver>>()
FirebaseFirestore.getInstance().collection("drivers").get().addOnSuccessListener { result ->
val listData = mutableListOf<Driver>()
Log.i("repo","called")
for (document in result) {
val photoUrl = document.getString("photoUrl")!!
val name = document.getString("name")!!
val team = document.getString("team")!!
Log.i("repo", "${document.id}} => ${document.data}")
val driver = Driver(name,team,photoUrl)
listData.add(driver)
}
mutableData.value = listData
}.addOnFailureListener {
Log.i("repo", "getDriverData: ${it.message}")
}
return mutableData
}
Your collection is actually called "drivers" - WITH the quotation marks. Whatever is generating the documents here is using extra quota when building the name of the collection.
You could read them back by adding those quotes:
FirebaseFirestore.getInstance().collection("\"drivers\"").get()
But you probably want to fix the code that generates the document to not add those quotes.

Caching is not working in Android Paging 3

I have implemented application using codelabs tutorial for new Paging 3 library, which was release week ago.
The problem is application is not working in offline mode. It does not retrieve data from Room database.
Tutorial Repo link :- https://github.com/googlecodelabs/android-paging
Code:-
RepoDao.kt
#Dao
interface RepoDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(repos: List<Repo>)
#Query("SELECT * FROM repos WHERE " +
"name LIKE :queryString OR description LIKE :queryString " +
"ORDER BY stars DESC, name ASC")
fun reposByName(queryString: String): PagingSource<Int, Repo>
#Query("DELETE FROM repos")
suspend fun clearRepos()
}
GithubRepository.kt
class GithubRepository(
private val service: GithubService,
private val database: RepoDatabase
) {
fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {
val dbQuery = "%${query.replace(' ', '%')}%"
val pagingSourceFactory = { database.reposDao().reposByName(dbQuery) }
return Pager(
config = PagingConfig(pageSize = NETWORK_PAGE_SIZE),
remoteMediator = GithubRemoteMediator(
query,
service,
database
),
pagingSourceFactory = pagingSourceFactory
).flow
}
companion object {
private const val NETWORK_PAGE_SIZE = 50
}
}
SearchRepositoriesViewModel.kt
#ExperimentalCoroutinesApi
class SearchRepositoriesViewModel(private val repository: GithubRepository) : ViewModel() {
private var currentQueryValue: String? = null
private var currentSearchResult: Flow<PagingData<Repo>>? = null
fun searchRepo(queryString: String): Flow<PagingData<Repo>> {
val lastResult = currentSearchResult
if (queryString == currentQueryValue && lastResult != null) {
return lastResult
}
currentQueryValue = queryString
val newResult: Flow<PagingData<Repo>> = repository.getSearchResultStream(queryString).cachedIn(viewModelScope)
currentSearchResult = newResult
return newResult
}
}
SearchRepositoriesActivity.kt
#ExperimentalCoroutinesApi
class SearchRepositoriesActivity : AppCompatActivity() {
.....
private lateinit var viewModel: SearchRepositoriesViewModel
private val adapter = ReposAdapter()
private var searchJob: Job? = null
// this is where adapter get flow data from viewModel
// initially this is called with **Android** as a query
private fun search(query: String) {
searchJob?.cancel()
searchJob = lifecycleScope.launch {
viewModel.searchRepo(query).collectLatest {
adapter.submitData(it)
}
}
}
.....
}
Output:- It is just showing the empty recyclerview when application is open in offline mode.
If you're able to share your code or how you reached that conclusion I could probably help pinpoint the problem a bit better, but the codelab does load data from Room on the branch: step13-19_network_and_database
There are two components here:
PagingSource: Provided by Room by declaring a #Query with a PagingSource return type, will create a PagingSource that loads from Room. This function is called in the pagingSourceFactory lambda in Pager which expects a new instance each call.
RemoteMediator: load() called on boundary conditions where the local cache is out of data, this will fetch from network and store in the Room db, which automatically propagates updates to PagingSource implementation generated by Room.
One other issue you might be seeing could be related to loadStateListener/Flow, essentially the codelab shows an error state by checking for CombinedLoadStates.refresh, but this always defers to the RemoteMediator's load state when available and if you want to show the locally cached data, even when RemoteMediator errors out, you'll need to disable hiding of the list in that case.
Note that you can access individual LoadState with CombinedLoadStates.source or CombinedLoadStates.mediator.
Hopefully this is enough to help you, but it's hard to guess your issue without some more concrete example / information about what you're seeing.
Edit: While the above are still good things to check for, it looks like there's an underlying issue with the library that I'm chasing down here: https://android-review.googlesource.com/c/platform/frameworks/support/+/1341068
Edit2: This is fixed now and will be released with alpha02.

Using a coroutine inside a Transform.map function

Well here is a tricky one.
I have a query from my Room DB. The result is a Relational POJO
class MessageWithMsgQueueAccount {
#Embedded
var message: MessageDto? = null
#Relation(parentColumn = "clientMessageId", entityColumn = "clientMessageId", entity = MessageQueueDto::class)
var messageQueueList: List<MsgQueueAccount> = ArrayList()
}
So when i get that i apply a Transformation to this object so i can create another one that has only the information that i want.
fun toContactMessageChatItem(item: MessageWithMsgQueueAccount?, accountDto: AccountDto): MessageChatItem {
item?.message?.let {
// Procedure for creating the fields that i want
val isQuoted = it.quotemsgid > 0L
if (isQuoted) {
// Fetch quoted message body
}
return MessageChatItem(.....)
} ?: run {
return MessageChatItem(..........)
}
}
Since this is a chat, one field that i want to setup is a quoted message body. What i have as "input" is the messageId of the message that is being quoted. So that means that i have to make a query to my Room DB again inside the transform function.
The way i did it is this
val isQuoted = it.quotemsgid > 0L
var quotedBody = ""
if (isQuoted) {
// Fetch quoted message body
viewModelScope.launch(Dispatchers.IO) {
val quotedMessage = messagesRepository.getMessageByMessageId(it.quotemsgid)
withContext(Dispatchers.Main) {
quotedBody = quotedMessage.body
}
}
}
There no specific question but is there any better way to do something like this, meaning querying the DB inside a Transformation function. Is there a way that this would create a synchronize problem to my elements or something?

Categories

Resources