I found a problem when (all) wallpaper is clicked, then the retrieved data is not the data it should be. Here's the application..
What the app looks like:
This is my firestore data with different name and secretariat in each wallpaper:
This is the code to retrieve data from the firestore into textViewContributor and textViewSekretariat located in FinalWallpaperActivity
lateinit var binding: ActivityInfoBinding
private var firestore = FirebaseFirestore.getInstance()
private lateinit var tvInputKontributor: TextView
private lateinit var tvInputSekretariat: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityInfoBinding.inflate(layoutInflater)
setContentView(binding.root)
tvInputKontributor = findViewById(R.id.inputKontributor)
tvInputSekretariat = findViewById(R.id.inputSekretariat)
firestore.collection("wallpaper")
.get()
.addOnSuccessListener { result ->
for (document in result) {
Log.d(TAG, "${document.id} => ${document.data}")
if (document != null) {
val name = document.data["name"].toString()
val sekretariat = document.data["sekretariat"].toString()
tvInputKontributor.text = name
tvInputSekretariat.text = sekretariat
}
}
}
.addOnFailureListener { exception ->
Log.d(TAG, "Error getting documents: ", exception)
}
This is the btnInfo declaration to open the InfoActivity
btnInfo.setOnClickListener {
val i = Intent(this, InfoActivity::class.java)
startActivity(i)
}
How to make each wallpaper have a different info description according to the clicked wallpaper? For my case all wallpapers have the same info description.. Whereas every contributor to the wallpaper should be different
you should be learning how to convert from json into array in kotlin ways and you should be working on Parcelable Data Class for eficiency data transfer.
Each pressed button/list item should passing a certain data object includes Contributor, Document ID,etc to a detail screen using Parcelable Data Class.
Related
I would like to display suggestions in recyclerView instead of embedded UI of the searchView (like on picture below)
Suggestions are loaded from API call on each text query change of my searchView.
Then, each time I receipt new suggestions, I'm executing an AsyncTask which populate the UI suggestions using Cursor
class FetchSearchUserTermSuggestionsTask(var searchview: SearchView) : AsyncTask<String?, Void?, Cursor>() {
override fun doInBackground(vararg params: String?): Cursor {
val cursor =
MatrixCursor(columnNamesArray)
var list : List<UserSuggestionDto>? = null
var call = RetrofitClient.userService.suggestionForTextSearch(0, 50, params[0]!!)
val response = call.execute()
if (response.isSuccessful) {
list = response.body()!!
} else {
//todo : throw exception
Log.e(EventAddFragment.TAG, response.message())
Log.e(EventAddFragment.TAG, response.errorBody().toString())
}
// parse your search terms into the MatrixCursor
if(list != null) {
for (index in list.indices) {
val term = list[index].fullname
val image = list[index].profilePic
val id = list[index].id
val row = arrayOf(index, id, image, term)
cursor.addRow(row)
}
}
return cursor
}
override fun onPostExecute(result: Cursor) {
searchview.suggestionsAdapter.changeCursor(result)
}
companion object {
private val columnNamesArray = arrayOf(BaseColumns._ID,
UserSuggestionDto.SUGGEST_COLUMN_ID,
UserSuggestionDto.SUGGEST_COLUMN_IMAGE,
UserSuggestionDto.SUGGEST_COLUMN_FULLNAME)
}
}
Finally, I prefer to populate a recyclerView in a dedicated fragment for research of user.
Is There a specific way to do it ?
My fears are :
RecyclerView is not designed to use Cursor and AsyncTask (Contrary to the searchView suggestions adapter)
So how to populate a recycler view from searchView on each text query change (by calling api each time) properly ?
Thanks in advance
I don't know why I got stuck in a problem that the chatList is not sorting by the last message time or by the most recent message. I have tried storing timestamp in the database and orderChildBy timestamp but it still not working.
This is the way I created chatList in the firebaseDatabase:
val timeAgo = Date().time
val myTimeMap = HashMap<String, Any?>()
myTimeMap["timestamp"] = timeAgo
myTimeMap["id"] = friendId
val friendTimeMap = HashMap<String, Any?>()
friendTimeMap["timestamp"] = timeAgo
friendTimeMap["id"] = currentUserID
val chatListSenderReference = dbRef.child("ChatList").child(currentUserID).child(friendId)
chatListSenderReference.keepSynced(true)
chatListSenderReference.addListenerForSingleValueEvent(object : ValueEventListener{
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
if(!p0.exists()){
chatListSenderReference.updateChildren(friendTimeMap)
}
val chatListReceiverReference = dbRef.child("ChatList").child(friendId).child(currentUserID)
chatListReceiverReference.updateChildren(myTimeMap)
}
})
on retrieving the chatlist in recyclerView
mUsers = ArrayList()
val userRef = dbRef.child("ChatList").child(currentUserID).orderByChild("timestamp")
userRef.addValueEventListener(object : ValueEventListener
{
override fun onCancelled(error: DatabaseError) {
}
override fun onDataChange(snapshot: DataSnapshot)
{
(mUsers as ArrayList).clear()
snapshot.children.forEach {
val userUid = it.key
if (userUid != null) {
(mUsers as ArrayList).add(User(uid = userUid))
}
}
retrieveGroupChatList()
chatListAdapter?.notifyDataSetChanged()
chatListAdapter = context?.let { ChatListAdapter(it, (mUsers as ArrayList<User>), true) }
recyclerViewChatList.adapter = chatListAdapter
}
})
this is the picture of the database, every time when I send or receive a message timestamp gets an update.
You need to specify another child after current user ID, because you have in each user id a list of IDs.
If you want to get accurate results, then you would have to change the type of your timestamp field from String to a number. You need this change is because the order of String values is lexicographically. So you should change this in the database, as well as in your class.
Do the below changes:
Get order by key
If you later want the time of the message(i.e. to display it in the chat message) then before pushing the changes to Firebase, store the time in your chat message Model.
Update your json structure by updating your chat message model to below,
ChatMessage(Your message model class)
-uid (id of the user that sent the message)
-name (name of the person that sent the chat message)
-message
-timestamp (time when user sent the message)
I have created a content retriever which calls data from the content provider in another application, I can read the data successfully, but I want to pass the data from the onLoadFinished Method in my content retriever to an activity which should display the data on button press. But I am unsure of how to do it, I tried using a getter and setter, but it does not print anything.
Here are the rows in my content provider:
String[] columns = new String[]{"_id", "item"};
MatrixCursor matrixCursor = new MatrixCursor(columns);
matrixCursor.addRow(new Object[]{1, "FOUND"});
//Test is a string object that gets serialized to Json
matrixCursor.addRow(new Object[]{2, new Gson().toJson(test)});
matrixCursor.setNotificationUri(getContext().getContentResolver(), uri);
return matrixCursor;
I want to get the data from the 2nd row and this is what I have in my onLoadFinished in my Retriever class:
class Retriever : LoaderManager.LoaderCallbacks<Cursor>{
private var dataFromJson = ""
//getters and setters
fun setData(data: String){
this.dataFromJson = data
}
fun getData():String{
return dataFromJson
}
//other Loader method such as onCreateLoader, etc.
override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) {
try
{
val success: Boolean
if (data != null && data.getCount() > 0)
{
data.moveToFirst()
val dataString = data.getString(1)
//this works, I am able to find the "found" text at the first row
success = dataString.equals("FOUND", ignoreCase = true)
//move to 2nd row
data.moveToNext()
//get data from 2nd row does not work
val JSonData = data.getString(1)
setData(JSonData)
}
else
{
Timber.d( "onLoadFinished Unknown error occurred")
success = false
}
//custom interface listener
listener.onClassListener(success)
}
catch (e:Exception) {
Timber.d("Error")
}
}
}
And in my activity when I create a Retriever object and call the getData() method, it displays nothing.
class TestActivity{
private lateinit var dataRetriever: Retriever
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_activity)
//I have a textView called dataTxt
//displays nothing
dataTxt.text = dataRetriever.getData()
}
}
Any help or advice will be highly appreciated
This is because of the loading task is an async operation, when you call the getData() function, the value of dataFromJson is probably still an empty string.
To easily deal with this, you can declare an interface like this:
interface OnDataRetrieveListener{
fun onDataLoad(dataString: String)
}
and then create an instance of it inside the Retriever
private lateinit var listener: OnDataRetrieveListener
fun setListener(listener: OnDataRetrieveListener){
this.listener = listener
}
set the listener in your activity (if anonymously, it's like below):
dataRetriever.setListener(object: Retriever.Listener{
override fun onDataLoad(data: String) {
dataTxt.text = dataRetriever.getData()
}
})
callback the listener when the string is loaded in Retriever:
...
val jsonData = data.getString(1)
listener.onDataLoad(jsonData)
...
then the dataTxt will update
I am building a notes app. This is the add note activity, It shows note successfully added while saving but after that raises the java.lang.IllegalStateException
'''
class AddNotesActivity : AppCompatActivity() {
private lateinit var titleED:EditText
private lateinit var descriptionED:EditText
private lateinit var saveNotesbutton:Button
private lateinit var realm: Realm
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_notes)
//init views
titleED = findViewById(R.id.title_Edittext)
descriptionED = findViewById(R.id.description_Edittext)
saveNotesbutton = findViewById(R.id.savenotesbutton)
realm = Realm.getDefaultInstance()
//set onclick listener
saveNotesbutton.setOnClickListener(){
addNotesToDB()
}
}
private fun addNotesToDB(){
try {
realm.beginTransaction()
val notes = Notes()
notes.title = titleED.text.toString()
notes.description = descriptionED.text.toString()
//notes.id = nextId
realm.commitTransaction()
Toast.makeText(this,"Notes added Successfully",Toast.LENGTH_SHORT).show()
startActivity(Intent(this,MainActivity::class.java))
finish()
//copy the transaction and commit
realm.copyToRealmOrUpdate(notes)
} catch (e:Exception){
Toast.makeText(this,"Failed $e",Toast.LENGTH_SHORT).show()
}
}
}
'''
I am trying to get unique numbers from call logs of a user and show them in a RecyclerView. By unique I mean if I have shown the number, or corresponding contact once in the list, I have to skip the item. And I want to use a Cursor for showing this to make the rendering quick.
To do this, I have implemented a RecyclerView Adapter which looks like this (Only showing the relevant code, kotlin)
class CallAdapter(val activity: Activity, var cursor: Cursor) : RecyclerView.Adapter<CallAdapter.ViewHolder>() {
val handler = Handler()
val contacts = ArrayList<Contact>()
init {
cursor.moveToFirst()
UniqueLogs(this).start()
}
companion object {
const val TAG = "CallAdapter"
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(activity)
val binding = CallLogItemBinding.inflate(inflater, parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int {
return contacts.count()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contact = contacts[position]
...
cursor.moveToNext()
}
class ViewHolder(val binding: CallLogItemBinding) : RecyclerView.ViewHolder(binding.root)
class UniqueLogs(val adapter: CallLogAdapterShobhit) : Thread() {
private var available = true
private val cursor = adapter.cursor
private val hash: HashMap<String, Int> = HashMap<String, Int>()
private val contacts = adapter.contacts
private var count = 0
override fun run() {
cursor.moveToFirst()
while (available && cursor.moveToNext()) {
var contact = adapter.getCurrentItemContact()
while (available && hash[contact.phoneNumber] == 1) {
if (cursor.moveToNext()) {
contact = adapter.getCurrentItemContact()
} else {
available = false
}
}
if (available) {
contacts.add(contact)
val position = count
count += 1
adapter.handler.post({
Log.d(TAG, "Position: $position Size: ${adapter.itemCount}")
adapter.notifyItemInserted(position)
})
hash[contact.phoneNumber] = 1
}
}
}
}
}
When I run this code, I am getting an exception which looks like this :-
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{c75176c position=5 id=-1, oldPos=4, pLpos:4 scrap [attachedScrap] tmpDetached not recyclable(1) no parent}
The position, id, oldPos, pLpos, etc. are different every time but the error is essentially the same.
I've tried writing logs as well to make sure that I am not trying to insert an element before the element is added to contacts array list, but the logs say that all's well.
Any idea what might be wrong here?
You are updating the data in a background thread, but notifying on the main thread. By the time the notification happens, you may have further altered the data. I would suggest that you do all your updates on the background and then just do a generic notifyDataSetChanged when everything is done.
Another option would be to calculate the entire new list in a background thread and then update the real data once and notify in the main thread.
Modifying the data and calling notifyItemInserted should synchronously or you will have inconsistent data.
Putting contacts.add(contact) just before notifyItemInserted should solve it.
adapter.handler.post({
Log.d(TAG, "Position: $position Size: ${adapter.itemCount}")
contacts.add(contact)
adapter.notifyItemInserted(position)
})