The bounty expires in 16 hours. Answers to this question are eligible for a +50 reputation bounty.
Fazal Hussain is looking for an answer from a reputable source:
The answer should contain the solution to how we can get suggestions from Algolia while user enters query
I am exploring the Algolia search suggestion, I am facing an issue that the search suggestion adapter is not getting refreshed when I start typing it keeps on showing the same suggestion, It should show the matching suggestion because I already connected the search view, Is there any extra thing I need to do I followed your documentation regarding Algolia search suggestion
Algolia Search Suggestion For Android
I think the query change listener is conflicting, check the code snippet below
Approach 1 Not Working
searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
showSuggestions()
showProducts()
}
Approach 2 Not Working
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
showSuggestions()
showProducts()
return true
}
})
Looks like you are calling both showSuggestions() and showProducts() methods same time, which could lead to the issue,
Can you try to modify the code as here and see?
searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
if (hasFocus) showSuggestions() else showProducts()
}
Also, make sure to use the setReorderingAllowed(true) method inside the showSuggestions() and showProducts() methods.
Related
I am new to Kotlin so please excuse this question, as it is probably pretty stupid...
So I followed the tutorial by Philipp Lackner to create a Todo-List as an android app if any of you know that. Now I tried to add a read and write functionality to this app by saving into a simple .txt-file for now.
For that I tried to follow this tutorial as much as possible, but now I am running into Problems when writing code to load the Todo-Items from a file.
I wrote this function to load Todo-Items from the .txt-file:
private suspend fun loadTodoItemsFromInternalStorage(): List<Todo> {
return withContext(Dispatchers.IO) {
val todoItemList: MutableList<Todo> = mutableListOf<Todo>()
var isEven = true
val files = filesDir.listFiles()
files?.filter { it.canRead() && it.isFile && it.name.endsWith(".txt") }?.map {
val lines = it.bufferedReader().readLines()
for (i in lines.indices) {
isEven = if(isEven) {
todoItemList.add(Todo(lines[i], lines[i+1].toBoolean()))
!isEven
} else {
!isEven
}
}
todoItemList
} ?: mutableListOf<Todo>()
}
}
Why do I get that type mismatch? I even initialize the list I want to return as a MutableList of type Todo, but I guess the type inference of Kotlin turns it into a MutableList of type Any?
So how do I fix this? And if you want to you could tell me better ways to do what I did (e.g. saving Todo-items (which consist of title and a boolean whether they are checked or not) to a file)
My plan to keep this as simple as possible as this is my first Kotlin project was to just use 2 lines for a Todo-item, where the first line is the title and the second line is the status whether it has been checked or not. I hope that makes my code easier to understand.
Thank you so much for your help in advance! I appreciate it a lot, as I have been struggling with coding in the past and really want to improve my coding skills.
If you cast your return statement, the code should work. Taking your code as a basis:
private suspend fun loadTodoItemsFromInternalStorage(): List<Todo> {
return withContext(Dispatchers.IO) {
...
} ?: mutableListOf<Todo>()
} as MutableList<Todo>
}
Some additional suggestions:
when dealing with file handling in general: error handling, check for correct format, empty entries, ...
the check for isEven/isOdd is obsolete and the code can be shortened to great extent when you use the step-option within the for-loop
for (i in lines.indices step 2) {
todoItemList.add(Todo(lines[i], lines[i+1].toBoolean()))
}
It is typical to set the inputType=textPassword for fields where users would type in anything that is at risk, like...a password. And when the user types it, each character they type is briefly flashed to let the user know what was actually entered and then turns into an asterisk so that casual observers (spies) have a lot more difficulty seeing what is typed.
Generally this works fine.
I have a specific case where for security reasons the character must NOT be flashed. Instead I want to simply display the asterisk.
I could handle each tap individually via setOnKeyListener(), but it seems like this should be easy. However I don't see any built-in solution. Please tell me that I'm missing something.
Thanks for all those who commented, prodded, and pointed out excellent tips. Using these valuable assists I was able to do something that was actually pretty easy and seems to work perfectly for this situation.
The trick is to override PasswordTransformationMethod and force your EditText to use this new one. The original charAt() method uses Spannable that waits a second and half before hiding it. By overriding that method your code can do something much simpler.
Here's an example of the custom class (in kotlin)
class ReallyHideMyPassword : PasswordTransformationMethod() {
companion object {
const val HIDE_CHAR = '*'
}
override fun getTransformation(source: CharSequence, view: View): CharSequence {
return PasswordCharSequence(source)
}
inner class PasswordCharSequence (private val source: CharSequence) : CharSequence {
override val length: Int
get() = source.length
override fun get(index: Int): Char = HIDE_CHAR
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return source.subSequence(startIndex, endIndex)
}
}
}
And to use this in your EditText:
val et = findViewById<EditText>(R.id.my_password_edittext)
et.transformationMethod = ReallyHideMyPassword();
Turns out that this is similar to https://stackoverflow.com/a/23110194/624814 (thanks Per.J!), but now you have it in kotlin. It's so similar that this question will probably be closed as too similar. But the wording of the question should help people find the answer more easily than the other post.
You are not missing anything, I've struggled with this in the past and until now I've found no built-in solution, I've used addTextChangedListener to do this and external libraries.
Disabling this setting does exactly what you asked, and it applies to every app but I guess you only can change this by code on a rooted phone.
I know this is no solution but it's a statement that this isn't available for developers and we have to make our own thing.
EDIT:
The comments offers great solutions but as you said, not built-in.
I had previously replaced SharedPreferences in my app with the new DataStore, as recommended by Google in the docs, to reap some of the obvious benefits. Then it came time to add a settings screen, and I found the Preferences Library. The confusion came when I saw the library uses SharedPreferences by default with no option to switch to DataStore. You can use setPreferenceDataStore to provide a custom storage implementation, but DataStore does not implement the PreferenceDataStore interface, leaving it up to the developer. And yes this naming is also extremely confusing. I became more confused when I found no articles or questions talking about using DataStore with the Preferences Library, so I feel like I'm missing something. Are people using both of these storage solutions side by side? Or one or the other? If I were to implement PreferenceDataStore in DataStore, are there any gotchas/pitfalls I should be looking out for?
For anyone reading the question and thinking about the setPreferenceDataStore-solution. Implementing your own PreferencesDataStore with the DataStore instead of SharedPreferences is straight forward at a glance.
class SettingsDataStore(private val dataStore: DataStore<Preferences>): PreferenceDataStore() {
override fun putString(key: String, value: String?) {
CoroutineScope(Dispatchers.IO).launch {
dataStore.edit { it[stringPreferencesKey(key)] = value!! }
}
}
override fun getString(key: String, defValue: String?): String {
return runBlocking { dataStore.data.map { it[stringPreferencesKey(key)] ?: defValue!! }.first() }
}
...
}
And then setting the datastore in your fragment
#AndroidEntryPoint
class AppSettingsFragment : PreferenceFragmentCompat() {
#Inject
lateinit var dataStore: DataStore<Preferences>
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = SettingsDataStore(dataStore)
setPreferencesFromResource(R.xml.app_preferences, rootKey)
}
}
But there are a few issues with this solution. According to the documentation runBlocking with first() to synchronously read values is the preferred way, but should be used with caution.
Make sure to setpreferenceDataStore before calling setPreferencesFromResource to avoid loading issues where the default implementation (sharedPreferences) will be used for initial loading.
A couple weeks ago on my initial try to implement the PreferenceDataStore, I had troubles with type long keys. My settings screen was correctly showing and saving numeric values for an EditTextPreference but the flows did not emit any values for these keys. There might be an issue with EditTextPreference saving numbers as strings because setting an inputType in the xml seems to have no effect (at least not on the input keyboard). While saving numbers as strings might work, this also requires reading numbers as strings. Therefore you lose the type-safety for primitive types.
Maybe with one or two updates on the settings and datastore libs there might be an official working solution for this case.
I have run into the same issue using DataStore. Not only does DataStore not implement PreferenceDataStore, but I believe it is impossible to write an adapter to bridge the two, because the DataStore uses Kotlin Flows and is asynchronous, whereas PreferenceDataStore assumes that both get and put operations to be synchronous.
My solution to this is to write the preference screen manually using a recycler view. Fortunately, ConcatAdapter made it much easier, as I can basically create one adapter for each preference item, and then combine them into one adapter using ConcatAdapter.
What I ended up with is a PreferenceItemAdapter that has mutable title, summary, visible, and enabled properties that mimics the behavior of the preference library, and also a Jetpack Compose inspired API that looks like this:
preferenceGroup {
preference {
title("Name")
summary(datastore.data.map { it.name })
onClick {
showDialog {
val text = editText(datastore.data.first().name)
negativeButton()
positiveButton()
.onEach { dataStore.edit { settings -> settings.name = text.first } }.launchIn(lifecycleScope)
}
}
}
preference {
title("Date")
summary(datastore.data.map { it.parsedDate?.format(dateFormatter) ?: "Not configured" })
onClick {
showDatePickerDialog(datastore.data.first().parsedDate ?: LocalDate.now()) { newDate ->
lifecycleScope.launch { dataStore.edit { settings -> settings.date = newDate } }
}
}
}
}
There is more manual code in this approach, but I find it easier than trying to bend the preference library to my will, and gives me the flexibility I needed for my project (which also stores some of the preferences in Firebase).
I'll add my own strategy I went with for working around the incompatibility in case it's useful to some:
I stuck with the preference library and added android:persistent="false" to all my editable preferences so they wouldn't use SharedPreferences at all. Then I was free to just save and load the preference values reactively. Storing them through click/change listeners → view model → repository, and reflecting them back with observers.
Definitely messier than a good custom solution, but it worked well for my small app.
It seems Android Studio/Gradle 3.4 has introduced a new lint error DiffUtilEquals. It is triggered by having a DiffUtil<Any> and then calling as a fallback oldItem == newItem in the areContentsTheSame function. The error the linter throws is
Suspicious equality check: equals() is not implemented in Object
Example code:
override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {
return when {
oldItem is MyKotlinClass && newItem is MyKotlinClass -> true
oldItem is MyKotlinClass2 && newItem is MyKotlinClass2 -> oldItem.title == newItem.title
else -> oldItem == newItem // Lint error on this line
}
}
This when statement would be pretty common in a DiffUtil for an Adapter that has multiple types where you compare each type based on their class.
What is the best way to handle this error? Should <Any> be changed to some interface that is like Equatable or maybe all the classes used in the adapter should implement some interface that includes a way to compare them?
All java.lang.Objects have an equals() function. It's a part of the base of the language. However, not all override it, and that's what the linter is triggering on. (SomeClass() as Any).equals(SomeClass()) will compile fine for an instance (assuming you have a class named SomeClass of course).
I couldn't reproduce this with just any class - it had to be the one you mentioned (DiffUtil.ItemCallback). I expanded the inspection, which says:
Suspicious equality check: equals() is not implemented in Object
Inspection info:areContentsTheSame is used by DiffUtil to produce diffs. If the method is implemented incorrectly, such as using identity equals instead of equals, or calling equals on a class that has not implemented it, weird visual artifacts can occur.
This answer is best demonstrated with a different snippet:
data class One(val t: String)
val x = object : DiffUtil.ItemCallback<One>() {
override fun areItemsTheSame(p0: One, p1: One): Boolean { TODO() }
override fun areContentsTheSame(p0: One, p1: One): Boolean {
return p0 == p1
}
}
This will compile. If you don't know, a data class generates a custom equals method. If you, in Android Studio, remove the data keyword, the error will reappear, because there is no overridden equals method.
TL;DR: The inspection complains about a lack of a custom equals method, and/or the use of identity checking (== in Java or === in Kotlin). However, === will raise a separate message which is considerably easier to actually identify the solution to:
Suspicious equality check: Did you mean == instead of ===?
And I imagine == in Java raises a similar message, but with equals as the suggested replacement. I have not verified this
As for solutions:
If you know what you're doing (NOT recommended)
You can suppress or change the severity away from error. Changing the severity or suppressing globally is in the same place. File -> Settings -> Editor -> Inspections > Android -> Lint -> Correctness -> Suspicious DiffUtil equality
Or you can suppress it locally with:
#SuppressLint("DiffUtilEquals")
If you want to play it safe
This is unfortunately more complicated.
Any has no guarantee equals is overridden - hence the inspection. The only viable option you really have is to use a different class. And using an Equatable isn't a bad idea either. However, unlike Any, this isn't implemented by default. In fact, it doesn't exist in the SDK. You can, however, create one yourself.
The catch is, any of the implementing classes need an equals method now. If you use data classes, this isn't a problem (and I've demonstrated this in the code). However, if you don't, you'll need to implement it manually. Manually here either means you write it, or you otherwise have it generated (for an instance with annotations).
interface Equatable {
override fun equals(other: Any?) : Boolean;
}
// Data classes generate the necessary equals methods. Equatable ensures all child classes do implement it, which fixes what the inspection wants you to fix.
data class One(val t: String) : Equatable
data class Two(val title: String) : Equatable
val x = object : DiffUtil.ItemCallback<Equatable /*Or a higher level inheritance model, i.e. a MySharedItemClass, where it either contains an abstract equals or implements Equatable. Implementing it doesn't require it to be abstract, but it depends on where you want the equals implementation. Equatable is an example of forced equals implementation, but there's many ways to Rome. */>() {
override fun areItemsTheSame(p0: Equatable, p1: Equatable): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(p0: Equatable, p1: Equatable): Boolean {
return when {
p0 is One && p1 is One -> true
p0 is Two && p1 is Two -> p0.title == p1.title
else -> p0 == p1 // No error!
}
}
}
bro all what you need is to override equals() in your POJO class and voila this error will disappear
I'm trying to write an Android app with Kotlin. Now, I want to show a counter in the ActionBar. I added an item called show_timer for that. Each second, it should count up by one:
override fun onWindowFocusChanged(hasFocus: Boolean) {
val item = findViewById(R.id.show_timer) as ActionMenuItemView
PublishSubject.interval(1, java.util.concurrent.TimeUnit.SECONDS, Schedulers.newThread())
.subscribeBy(onNext = {item.text = it.toString()})
super.onWindowFocusChanged(hasFocus)
}
But somehow this doesn't work. It updates the default text to 0, but after that it does nothing. Does someone know why this doesn't work?
Thank you in advance,
Niklas
In order for the text to update, it needs to be updated on the main thread (not the Schedulers.newThread() one)
Adding:
.observeOn(AndroidSchedulers.mainThread())
Should fix things, and get the label to update