I'm using firebase in my project and i'm testing all the functionalities which includes firebase components and i recently tried to test firebase database but it is throwing an error which i could not understand if someone could help , i would appreciate it , Thank you
error that i'm getting
Attempt to invoke virtual method 'com.google.firebase.database.DatabaseReference com.google.firebase.database.DatabaseReference.child(java.lang.String)' on a null object reference
at com.revert.journey.app.chatui.ChatHomeActivityTest.testCaseSendMessage
This is my testing code
#Test
fun testCaseSendMessage(){
val databaseMock = mock(DatabaseReference::class.java)
ServiceLocator.reference = databaseMock
`when`(databaseMock.child("Messages").child("1251515").setValue(Utils.message()))
.thenReturn(isNotNull())
}
This is my real code
val messageMap = hashMapOf<String,Any>()
messageMap["userName"] = userName
messageMap["userMessage"] = message
messageMap["userPic"] = userPic
messageMap["messageTiming"] = Calendar.getInstance().timeInMillis.toString()
messageMap["chatImage"] = downloadUrl
messageMap["uid"] = firebaseAuth.currentUser!!.uid
ServiceLocator.reference
.child("Messages")
.child(System.currentTimeMillis().toString())
.setValue(messageMap).await()
ServiceLocator class
object ServiceLocator {
var firebaseAuth = FirebaseAuth.getInstance()
var reference = FirebaseDatabase.getInstance().reference
}
Image Sample from my firebase
UPDATE
var base = ServiceLocator.reference.child("Messages")
var child = base.child(System.currentTimeMillis().toString())
child.setValue(messageMap).await()
If you want to Mock the full call chain you could create mocks for the intermediate states like this (I don't know all the right types to use here for realtime database, but a similar approach works for Firestore)
val databaseMock = mock(DatabaseReference::class.java)
val childMock = mock(Reference::class.java)
val mockTask = mock(??) // set type to whatever "setValue" returns
doReturn(childMock).`when`(databaseMock).child(anyString())
doReturn(childMock).`when`(childMock).child(anyString())
doReturn(mockTask).`when`(childMock).setValue(any())
If you want to actually test that the correct value was set, you can add a listener to the mock to intercept the actual value passed to it
doAnswer { invocation ->
val args = invocation.arguments
val l = args[0] as Map<String,Any>
//add tests here to assert that the map values you sent are correct
null
}.`when`(childMock).setValue(any())
Debugging Tips
If you want to diagnose what is going on in a scenario like this you can change the chained call in your real code to something like the code below. Then if one of the calls returns null you will know exactly which one it is and can add the missing mock for it.
val db = ServiceLocator.reference
val cm = db.child("Messages")
val ct = cm.child(System.currentTimeMillis().toString())
val response = ct.setValue(messageMap)
response.await()
None of these call should access your database in the test, so the actual database schema does not matter. All that matters is getting the mocks set correctly (since you are using a mock database anyway)
Related
I got a StateFlow of type UserStateModel (data class) in my app.
private val _userStateFlow: MutableStateFlow<UserStateModel?> = MutableStateFlow(UserStateModel())
val userStateFlow: StateFlow<UserStateModel?> = _userStateFlow
here is the UserStateModel
data class UserStateModel(
val uid: String? = null,
val username: String? = null,
val profileImageUrl: String? = null,
var isLoggedIn: Boolean = false,
val isPremiumUser: Boolean = false,
val posts: List<Post>? = listOf()
)
When I update the StateFlow with a new Username it emits the change to the collectors and the UI updates.
But when I change a property inside the posts: List? list it doesnt emit the changes.
When I change the size of the list it does, when I change the name property of the Post at index 0 it doesnt.
How can I detect changes to the child properties of the Data class?
Right now I use an ugly workaround, I add
val updateErrorWorkaround: Int = 0
to the UserStateModel data class and increase it by one so the collectors get notified
P.s I'm using MVVM + Clean Architecture and Jeptack Compose
EDIT
Thats my Post Model:
data class Post(
val id: Int,
val name: String,
val tags: MutableList<Tag>? = null
)
Here is how I update the MutableList:
val posts = userStateFlow.value?.posts
posts.get(index).tags?.add(myNewTag)
_userStateFlow.value = userStateFlow.value?.copy(posts = posts)
Those changes are not emitted to the collectors
StateFlow emits only if it detects changes to the value, it ignores replacing the value with the same data. To do this, it compares the previous value with the new one. For this reason, we shouldn't modify the data that we already provided to the StateFlow, because it won't be able to detect changes.
For example, we set value to a User(name=John). Then we mutate the same user object by modifying its name to James and we set the value to this "new" user object. StateFlow compares "new" User(name=James) with its stored value, which is now also User(name=James), so it doesn't see any changes.
In your example you created a copy of UserStateModel, but inside you re-use the same objects and you mutate them. In this case you added a new item to tags and this change affected old UserStateModel as well, so StateFlow doesn't detect the change.
To fix the problem, you need to copy all the data that was changed and do not mutate anything in-place. It is safer to make all the data immutable, so val and List - this way you are forced to make copies. I changed tags to val tags: List<Tag> = listOf(), then your code could look like the following:
val posts = userStateFlow.value?.posts!!.toMutableList()
posts[index] = posts[index].copy(tags = posts[index].tags + myNewTag)
userStateFlow.value = userStateFlow.value?.copy(posts = posts)
Here we create a copy of not only UserStateModel. We also copy posts list, the Post that we modify and we also copy the list of tags.
Alternatively, if this behavior of StateFlow is more annoying to you than helpful, you can use SharedFlow which doesn't compare values, but just emits.
I'm trying to run a query in Firebase to get the value of an specific field in the USERS collection and I don't understand why .documents it's an Unresolved Reference. Any ideas?
fun getUserSpecialty() {
val user = FirebaseAuth.getInstance().currentUser!!.uid
val specRef = usersCollectionRef.document(user)
specRef.get().addOnSuccessListener { snapshot ->
for (document in snapshot.documents) { //.documents it's an Unresolved Reference
val data = document.data
val userSpecialtyCode = data!![SPECIALTY_CODE] as String
val loggedUserSpecialty = UserSpecialty(userSpecialtyCode)
userSpecClass.add(loggedUserSpecialty)
this.userSpecTxt?.text = userSpecialtyCode
}
}
}
It's because snapshot is a DocumentSnapshot, and as you can see from the linked API documentation, it doesn't have a method called getDocuments() on it.
When you call get() on a DocumentReference as you are now, you get a single document as a DocumentSnapshot. You do not get a QuerySnapshot like you do with queries that could return multiple documents. You are probably confusing the two.
I'm trying to slove this problem so long time.
First time , I got the javaMail.Message. I pull the content of mail message and other things in RecyclerView.Adapter -> OnBindViewHolder. But When I pulling content and managing flag , it take so long time.
this is main page
val props = Properties()
props.setProperty("mail.debug", "true")
props.setProperty("mail.imap.port", "143")
props.setProperty("mail.imap.ssl.enable", "true")
props.setProperty("mail.imap.socketFactory.port", "993")
val session = Session.getDefaultInstance(props, authenticator(mailBox.user, mailBox.pass))
val store = session.getStore("imaps")
store.connect(mailBox.mailHost,mailBox.user,mailBox.pass)
val fetchProfile = FetchProfile()
fetchProfile.add(FetchProfile.Item.CONTENT_INFO)
fetchProfile.add(FetchProfile.Item.ENVELOPE)
val emailFolder = store.getFolder("Inbox")
emailFolder.open(Folder.READ_WRITE)
val messages: Array<Message> = emailFolder.messages
emailFolder.fetch(messages,fetchProfile)
activityUiThread {
val adapter = testAdapter(this#MailBox,messages,this#MailBox)
allMail_recycler.adapter = adapter
dialog.dismiss()
}
this is adapter
override fun onBindViewHolder(holder : ViewHolder, position: Int) = runBlocking{
val message = receiveMessage[position]
/** visibility **/
// holder.itemView.d3p.visibility = INVISIBLE
holder.itemView.imV_attach.visibility = INVISIBLE
/** unChangeable **/
val bworker = GlobalScope.launch {
val from = message.from[0].toString().split("<")[0]
val sentDate= message.receivedDate
val subject = message.subject
val content = message.content.toString()
val recepient = message.allRecipients[0].toString()
val seen = message.flags.toString()
val attachent = message.isMimeType("multipart/mixed")
if (attachent){
holder.itemView.imV_attach.visibility = VISIBLE
}
holder.itemView.mailSender.text = from
holder.itemView.dateTimeTxt.text = SimpleDateFormat("MMM dd").format(sentDate)
holder.itemView.subjectTxt.text = subject
holder.itemView.bodyTxt.text = content
// mailBox.data.add(messageModel(from,recepient,subject,content,date,attachent,seen))
}
bworker.join()
holder.itemView.setOnClickListener {
notifyItemChanged(position)
eventChange.change(position)
}
}
this is my project
check this
Let's start by simplifying your code. Replace these lines:
props.setProperty("mail.debug", "true")
props.setProperty("mail.imap.port", "143")
props.setProperty("mail.imap.ssl.enable", "true")
props.setProperty("mail.imap.socketFactory.port", "993")
val session = Session.getDefaultInstance(props, authenticator(mailBox.user, mailBox.pass))
with these:
props.setProperty("mail.debug", "true")
props.setProperty("mail.imap.ssl.enable", "true")
val session = Session.getInstance(props)
This:
val from = message.from[0].toString().split("<")[0]
probably wants to be something like this:
val from = (InternetAddress)(message.from[0]).getAddress()
(Not sure if the cast is needed.)
I don't know why you're using the received data as the sent date, but that's wrong. Why aren't you just using the sent date?
Using message.content.toString is only going to work for a simple plain text message. Certainly any message with an attachment is not going to give you want you want. See the FAQ entry for
find the main message body.
That will also explain why you don't want to pull down the entire message content every time; it's horribly inefficient. You might also need to change your program so that any attachments are only downloaded when the user clicks on them. For debugging, try commenting out the access of message.content and replace it with an empty string and see if the performance is closer to what you're expecting.
If you're still not getting the desired performance, you should look at the other FetchProfile items you might use. You should also post the JavaMail debug output, ideally with a timestamp for each line, or with output from your program showing System.currentTimeMillis() around each one of the key statements in your program.
I have the following code
lateinit var roomdb:RoomDb
val penLiveData:LiveData<Pen> = roomdb.getPen()
val paperLiveData:LiveData<Paper> = roomdb.getPaper()
val rulerLiveData:LiveData<Ruler> = roomdb.getRuler()
...
As you might expect I am getting an exception that roomdb was not initialized. How do I fix this without too much complication? And please in your response take into account that I have multiple liveData fields that depend on roomdb and I don't want to have to do a bunch of work for each one of them.
roomdb is necessarily initialized by a method call, not inside init.
There is a way to do it with `Transformations.switchMap.
It feels a bit like someone just asked why the gun pointing at their foot doesn't fire. This is me telling you that the safety is still on, what you do to your foot is your business.
Basically, you have a LiveData that will kick of the other's to fetch/refresh.
private val _loadData = MutableLiveData<Boolean>()
val penLiveData = Transformations.switchMap(_loadData) { roomdb.getPen() }
//...
Then you set _loadData.value = true after ensuring that roomdb is set.
Maybe the following is a little cleaner?
private val _loadData = MutableLiveData<RoomDb>()
val penLiveData = Transformations.switchMap(_loadData) { it?.getPen() }
//...
and
_loadData.value = roomdb
or, if within an asynchronous context.
_loadData.postValue(roomdb)
You probably want to return some Default or NullObject (like AbsentLiveData from AAC samples) if switchMap parameter is null.
I'm using instant search on Android and I'm trying to filter my index by one Id and the name of the object.
The thing is I'm using the InstantSearch object and the searcher. If adding the filter(ID) to the query, the response is always blank.
Here is my code:
private val searcher = Searcher.create(APP_ID, API_KEY, INDEX_OBJECT)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
setSupportActionBar(tbSearch)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_arrow_back_normal_48px)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
val q = Query()
q.filters = "id:Hj5RFIzLjk5XTJOdODeu"
searcher.query = q
val helper = InstantSearch(this, searcher)
helper.search()
}
Your Query object is initialized with an empty constructor.
See this line in your code.
val q = Query()
Add query string like this.
val q = Query("Your Query String")
See here for more info: https://github.com/algolia/algoliasearch-client-android#search
You are using InstantSearch Android correctly, this is the expected way to pass a filter with an empty query.
Have you tried the same query in your dashboard to see if it brings the same result? You might have forgotten to enable faceting on this id attribute.