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
Related
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.
I would like to sort my items in order to put at the beginning chosen elements, I created a small piece of code allowing it which works rather well, but when I update my array in the adapter, the recyclerview is unchanged. I tried to do it with an update function in my adapter, and with the debugger, I see that the passed array is modified but nothing in the view... I also tried to send my array when creating the adapter but nothing changes. More weird, because I also have a filter by alphabetical order which works well.... Anyone have an idea? Thanks
Here's is my sort code which work, I tested it with the debugger:
for (i in 0 until folders.size) {
val isRead: String? = prefNewDir.getString(folders[i].absolutePath, null)
if (isRead != null) {
println("before : "+folders[i] + "et i :" + i)
val old= folders[i]
folders.removeAt(i)
folders.add(0, old)
println("after : "+folders[i] + "et i :" + i)
}
}
Here my update method in my adapter:
fun updateData(data: MutableList<File>) {
println("update before : "+items[0])
items = data
println("update after : "+items[0])
notifyDataSetChanged()
}
Here's my adapter code:
class DirAdapter(
private val context: Context,
private var items: MutableList<File>,
) : RecyclerView.Adapter<DirAdapter.DirViewHolder>() {
private val prefNewDir: SharedPreferences = context.getSharedPreferences("new_files", Context.MODE_PRIVATE)
class DirViewHolder(view : View) : RecyclerView.ViewHolder(view) {
val Dir_name = view.findViewById<TextView>(R.id.file_name)
val DirIsRead = view.findViewById<ImageView>(R.id.file_new)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType : Int):DirViewHolder{
val view : View = LayoutInflater.from(parent.context) //Mode dossier
.inflate(R.layout.folder_element, parent, false)
return DirViewHolder(view)
}
fun updateData(data: MutableList<File>) {
println("update data : "+items[0])
items = data
println("update data : "+items[0])
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: DirViewHolder, position: Int) {
val currentDir = items[position]
holder.Dir_name.text = currentDir.name
val isRead: String? = prefNewDir.getString(currentDir.absolutePath, null) // On recupere si oui ou non le fichier est lu
if (isRead!=null) {
holder.DirIsRead.visibility = View.VISIBLE
} else
holder.DirIsRead.visibility = View.GONE
holder.itemView.setOnClickListener {
val intent = Intent(context, FilesActivity::class.java)
intent.putExtra("path",currentDir.absolutePath)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}
holder.itemView.setOnLongClickListener {
val popupMenu = PopupMenu(context, it)
if (isRead != null) { //Popup seulement sur un element
popupMenu.menu.add("Marquer comme lu")
}
popupMenu.setOnMenuItemClickListener {
if (it.title == "Marquer comme lu") {
putFolderAsRead(currentDir)
holder.DirIsRead.visibility = View.GONE
}
true
}
popupMenu.show()
true
}
}
private fun putFolderAsRead(folder: File) {
val editor: SharedPreferences.Editor = prefNewDir.edit()
editor.remove(folder.absolutePath)
//Dossier courant
val folderList = folder.listFiles()
for (dListItem in folderList) {
if(dListItem.isDirectory) //Si un dossier est present on reboucle dessus
putFolderAsRead(dListItem)
editor.remove(dListItem.absolutePath)
println(dListItem.absolutePath + " Marque comme lu !")
}
editor.apply()
}
override fun getItemCount() = items.size
}
First of all for storing data in shared preferences, I'd prefer you to use my library here. You can refer the docs to see how it works. Also, I'd prefer you to use a data class instead of the File class. That won't give you what you want. Since, you already show the files without sort, you just need some modifications:
Make a data class like this:
data class MyFile(var file: File, var isNew: Boolean = false)
Now, the sorting part will be like this:
var folders = getFolders(Environment.getExternalStorageDirectory())
for (i in 0 until folders.size) {
val isRead: String? = prefNewDir.getString(folders[i].absolutePath, null)
folders.get(i).isNew = if(isRead == null) false else true
}
Now also, you might want to get the folder without sorting and you might be a bit confused, so, here is the code for getting the folders
fun getFolders(location:File) : ArrayList<MyFile>(){
val files = location.listFile()
var foldersList = ArrayList<MyFile>()
for (file in files) {
if(file.isDirectory){ foldersList.add(MyFile(file)) }
}
}
This will do the task for you.
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 need to show a custom info window when I click on a pin.
The pin is directly in style on the layer.
When I creating a map Im getting the style by url:
mapView?.getMapAsync { map ->
map.setStyle(Style.Builder().fromUrl("mapbox://styles/my-style")) {
onMapReady(map)
}
}
Then I define this layer:
fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
val layer = mapboxMap.style?.getLayer("my-layer")
layer?.setProperties(visibility(Property.VISIBLE))
mapboxMap.addOnMapClickListener(this#InfoWindowSymbolLayerActivity)
}
OnMapClick method:
override fun onMapClick(point: LatLng): Boolean {
return mapboxMap?.projection?.toScreenLocation(point)?.let { handleClickIcon(it) }!!
}
HandleClickIcon method:
fun handleClickIcon(screenPoint: PointF): Boolean {
val features = mapboxMap?.queryRenderedFeatures(screenPoint, MARKER_LAYER_ID)
val inflater = LayoutInflater.from(this)
val bubbleLayout = inflater.inflate(R.layout.pin_info, null) as BubbleLayout
val type = features?.get(0)?.getStringProperty(type)
bubbleLayout.tvDefectType.text = type?.let { formatType(it) }
val username = features?.get(0)?.getStringProperty(username)
bubbleLayout.tvDefectInfo.text = username?.let { formatDefectInfo(it) }
val measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
bubbleLayout.measure(measureSpec, measureSpec)
val measuredWidth = bubbleLayout.measuredWidth.toFloat()
bubbleLayout.arrowPosition = measuredWidth / 2 - 5
val bitmap = SymbolGenerator.generate(bubbleLayout)
type?.let { mapboxMap?.style?.addImage(it, bitmap) }
mapboxMap?.let {
it.getStyle { style ->
setUpInfoWindowLayer(style)
}
}
return true
}
Mapbox example uses custom GeoJson:
https://docs.mapbox.com/android/maps/examples/symbol-layer-info-window/
But I need to display the info window above the
pin like this on click
If the layer is already in your style, then you don't need to set its visibility to visible with layer?.setProperties(visibility(Property.VISIBLE)). It's already going to be visible.
I'd follow https://docs.mapbox.com/android/maps/examples/symbol-layer-info-window/ by:
Starting the data loading and bubble window SymbolLayer setup earlier in your code, rather than in onMapClick: https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/dds/InfoWindowSymbolLayerActivity.java#L88
Only changing the select state in onMapClick: https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/dds/InfoWindowSymbolLayerActivity.java#L186-L206
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)
})