Android SearchView onQueryTextChange doesn't work with ObservableOnSubscribe - kotlin

I've got such code for searching streams by their name.
compositeDisposable.add(Observable.create(ObservableOnSubscribe<String> { subscriber ->
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String?): Boolean {
subscriber.onNext(newText!!)
return false
}
override fun onQueryTextSubmit(query: String?): Boolean {
subscriber.onNext(query!!)
return false
}
})
})
.debounce(250, TimeUnit.MILLISECONDS)
.distinct()
.subscribe { text ->
val pagerPos = binding.streamsPager.currentItem
val currentFragment =
activity?.supportFragmentManager?.findFragmentByTag("f${pagerAdapter.getItemId(pagerPos)}")
(currentFragment as StreamFragment).onSearchHolder.onSearch(text)
}
)
It works fine with onQueryTextSubmit, but ignores setOnQueryTextListener. So when I want to erase symbols and "reset" search I get nothing
How to fix this?

use a wrapper class for your ObservableOnSubscribe and sealed class for your search events. Like this:
class SearchViewWrapper(private val searchView: SearchView) : ObservableOnSubscribe<SearchViewEvent> {
override fun subscribe(emitter: ObservableEmitter<SearchViewEvent>) {
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
emitter.onNext(SearchViewEvent.onQueryTextSumbit(query))
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
emitter.onNext(SearchViewEvent.onQueryChanged(newText))
return true
}
})
}
}
Sealed class for events:
sealed class SearchViewEvent {
data class onQueryChanged(val text: String?): SearchViewEvent()
data class onQueryTextSumbit(val query: String?): SearchViewEvent()
}
Try to use it where you need(just example):
fun useIt(searchView: SearchView) {
Observable.create(SearchViewWrapper(searchView))
.subscribe(::handleSearchViewEvent)
.addTo(yourCompositeDisposable)
}
private fun handleSearchViewEvent(event: SearchViewEvent) {
when (event) {
is SearchViewEvent.onQueryChanged -> TODO()
is SearchViewEvent.onQueryTextSumbit -> TODO()
}
}

Related

Filter searchView from RecycleView with Adapter

Adapter class
class AppListAdapter(private val context: Context, initialChecked: ArrayList<String> = arrayListOf()) : RecyclerView.Adapter<AppListAdapter.AppViewHolder>() {
public val appList = arrayListOf<ApplicationInfo>()
private val checkedAppList = arrayListOf<Boolean>()
private val packageManager: PackageManager = context.packageManager
init {
context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA).sortedBy { it.loadLabel(packageManager).toString() }.forEach { info ->
if (info.packageName != context.packageName) {
if (info.flags and ApplicationInfo.FLAG_SYSTEM == 0) {
appList.add(info)
checkedAppList.add(initialChecked.contains(info.packageName))
}
}
}
}
inner class AppViewHolder(private val item: ItemAppBinding) : RecyclerView.ViewHolder(item.root) {
fun bind(data: ApplicationInfo, position: Int) {
item.txApp.text = data.loadLabel(packageManager)
item.imgIcon.setImageDrawable(data.loadIcon(packageManager))
item.cbApp.isChecked = checkedAppList[position]
item.cbApp.setOnCheckedChangeListener { _, checked ->
checkedAppList[position] = checked
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder {
return AppViewHolder(ItemAppBinding.inflate(LayoutInflater.from(context), parent, false))
}
override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
holder.bind(appList[position], position)
}
override fun getItemCount(): Int {
return appList.size
}
on MainActivity
binding.searchView2.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
binding.searchView2.clearFocus()
// how to write code filtered by query?
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
// how to write code filtered by newText?
return false
}
})
I'm newbie in kotlin..anyone can help?
I believe you want to filter items with "ApplicationInfo.txApp", so i will write the code for it.
First you need your adapter class to extend Filterable like below, and add one more list to hold all items:
class AppListAdapter(private val context: Context, initialChecked: ArrayList<String> = arrayListOf()) : RecyclerView.Adapter<AppListAdapter.AppViewHolder>(), Filterable {
public val appList = arrayListOf<ApplicationInfo>()
public val appListFull = ArrayList<ApplicationInfo>(appList)
// This full list because of when you delete all the typing to searchView
// so it will get back to that full form.
Then override it's function and write your own filter to work, paste this code to your adapter:
override fun getFilter(): Filter {
return exampleFilter
}
private val exampleFilter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults? {
val filteredList: ArrayList<ApplicationInfo> = ArrayList()
if (constraint == null || constraint.isEmpty()) {
// when searchview is empty
filteredList.addAll(appListFull)
} else {
// when you type something
// it also uses Locale in case capital letters different.
val filterPattern = constraint.toString().lowercase(Locale.getDefault()).trim()
for (item in appListFull) {
val txApp = item.txApp
if (txApp.lowercase(Locale.getDefault()).contains(filterPattern)) {
filteredList.add(item)
}
}
}
val results = FilterResults()
results.values = filteredList
return results
}
#SuppressLint("NotifyDataSetChanged") #Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
appList.clear()
appList.addAll(results!!.values as ArrayList<ApplicationInfo>)
notifyDataSetChanged()
}
}
And finally call this filter method in your searchview:
binding.searchView2.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
yourAdapter.filter.filter(query)
yourAdapter.notifyDataSetChanged()
binding.searchView2.clearFocus()
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
yourAdapter.filter.filter(newText)
yourAdapter.notifyDataSetChanged()
return false
}
})
These should work i'm using something similar to that, if not let me know with the problem.

how to play audio offline by url?

i want to if i press play - the audio start playing, but if I will turn off the internet after some time, I can still listen to the audio to the end, how can i do this using andoid-mediaPlayer?
in kotlin
class MusicService:Service(),MediaPlayer.OnPreparedListener,MediaPlayer.OnCompletionListener,MediaPlayer.OnErrorListener {
override fun onBind(intent: Intent?): IBinder? {
return musicBind
}
override fun onCreate() {
super.onCreate()
player= MediaPlayer()
}
fun initMusic(){
player.setWakeMode(applicationContext,PowerManager.PARTIAL_WAKE_LOCK)
player.setAudioStreamType(AudioManager.STREAM_MUSIC)
player.setOnPreparedListener(this)
player.setOnCompletionListener(this)
player.setOnErrorListener(this)
}
override fun onUnbind(intent: Intent?): Boolean {
player.stop()
player.reset()
player.release()
return false
}
inner class MusicBinder:Binder(){
val service: MusicService
get() = this#MusicService
}
companion object{
const val STOPPED = 0
const val PAUSED =1
const val PLAYING = 2
}
override fun onPrepared(mp: MediaPlayer?) {
mp!!.start()
val duration=mp.duration
seekBar.max=duration
seekBar.postDelayed(progressRunner,instentval.toLong())
end_point.text= String.format("%d:%02d",TimeUnit.MILLISECONDS.toMinutes(duration.toLong()),
TimeUnit.MILLISECONDS.toSeconds(duration.toLong())-
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration.toLong())))
}
private fun playSong(){
try {
player.reset()
val playSong=songs
val uri = playSong.link
player=MediaPlayer.create(this, Uri.parse(uri))
try {
player.start()
progressRunner.run()
}catch (e:HttpException){
Toast.makeText(this,"Http went wrong!",Toast.LENGTH_SHORT).show()
}
} catch (e:Exception){
Toast.makeText(this,"something went wrong!",Toast.LENGTH_SHORT).show()
}
}
fun setUI(seekBar: SeekBar,start_int:TextView,end_int:TextView){
this.seekBar=seekBar
start_point=start_int
end_point=end_int
seekBar.setOnSeekBarChangeListener(object :SeekBar.OnSeekBarChangeListener
{
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser){
player.seekTo(progress)
}
start_point.text = String.format(
"%d:%02d",TimeUnit.MILLISECONDS.toMinutes(progress.toLong()),
TimeUnit.MILLISECONDS.toSeconds(progress.toLong())-
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(progress.toLong())))
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
TODO("Not yet implemented")
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
TODO("Not yet implemented")
}
}
)
}
private var progressRunner:Runnable = object :Runnable {
override fun run() {
if (seekBar!=null){
seekBar.progress=player.currentPosition
if (player.isPlaying){
seekBar.postDelayed(this,instentval.toLong())
}
}
}
}
fun setSong(result: Result){
songs=result
playerState= PLAYING
playSong()
}
fun setSongmp3(mp3: Mp3){
songsmp3=mp3
playerState= PLAYING
playSongmp3()
}
public fun puseSong()
{
player.pause()
playerState = PAUSED
}
public fun resumeSong(){
player.start()
playerState= PLAYING
}
override fun onCompletion(mp: MediaPlayer?) {
}
override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
return false
}
}

How to set up Viewmodel class properly?

I'm trying to follow some tutotial from github about MVVM model and i'm stuck at viewmodel class because there's an error says
Not enough information to infer type variable T
and
Type mismatch.
Required:Resource<Movie>
Found:Unit
And when i check my other class like ApiService, Dao, NetworkBoundResource, ApiResponse, Resources and respository everthing fine like this
ApiService :
interface ApiService {
#GET("3/movie/popular")
fun getMyMovie(#Query("api_key") api : String = "32bbbffe944d16d1d2a3ee46cfc6aaa0"
) : Flow<ApiResponse<MovieResponse.Movie>>
}
MovieDao:
#Dao
interface MovieDao : BaseDao<Movie> {
// #Insert(onConflict = OnConflictStrategy.REPLACE)
// fun insertMovie(movie: List<Movie>)
#Query("SELECT * FROM `movie` ORDER by movie_id DESC")
fun getMyMovie() : Flow<Movie>
#Query("SELECT * FROM `movie` ORDER by movie_id DESC")
fun findAllMovie() : Maybe<List<Movie>>
#Query("SELECT * FROM `movie` ORDER by movie_id DESC")
fun streamAll() : Flowable<List<Movie>>
#Query("DELETE FROM `movie`")
fun deleteAll()
}
MovieRespository:
class MovieRespository (val apiService: ApiService, val movieDao: MovieDao) {
fun getListMovie() : Flow<Resource<Movie>> {
return networkBoundResource(
fetchFromLocal = { movieDao.getMyMovie() },
shouldFetchFromRemote = {true},
fetchFromRemote = {apiService.getMyMovie()},
processRemoteResponse = {},
saveRemoteData = {movieDao.insert(
it.results.let {
it.map { data -> Movie.from(data) }
}
)},
onFetchFailed = {_, _ ->}
).flowOn(Dispatchers.IO)
}
NeteorkBoundResource:
inline fun <DB, REMOTE> networkBoundResource(
crossinline fetchFromLocal: () -> Flow<DB>,
crossinline shouldFetchFromRemote: (DB?) -> Boolean = { true },
crossinline fetchFromRemote: () -> Flow<ApiResponse<REMOTE>>,
crossinline processRemoteResponse: (response: ApiSuccessResponse<REMOTE>) -> Unit = { Unit },
crossinline saveRemoteData: (REMOTE) -> Unit = { Unit },
crossinline onFetchFailed: (errorBody: String?, statusCode: Int) -> Unit = { _: String?, _: Int -> Unit }
) = flow<Resource<DB>> {
emit(Resource.Loading(null))
val localData = fetchFromLocal().first()
if (shouldFetchFromRemote(localData)) {
emit(Resource.Loading(localData))
fetchFromRemote().collect { apiResponse ->
when (apiResponse) {
is ApiSuccessResponse -> {
processRemoteResponse(apiResponse)
apiResponse.body?.let { saveRemoteData(it) }
emitAll(fetchFromLocal().map { dbData ->
Resource.Success(dbData)
})
}
is ApiErrorResponse -> {
onFetchFailed(apiResponse.errorMessage, apiResponse.statusCode)
emitAll(fetchFromLocal().map {
Resource.Error(
apiResponse.errorMessage,
it
)
})
}
}
}
} else {
emitAll(fetchFromLocal().map { Resource.Success(it) })
}
}
ApiResponse:
sealed class ApiResponse<T> {
companion object {
fun <T> create(error: Throwable): ApiErrorResponse<T> {
return ApiErrorResponse(
error.message ?: "Unknown error",
0
)
}
fun <T> create(response: Response<T>): ApiResponse<T> {
return if (response.isSuccessful) {
val body = response.body()
val headers = response.headers()
if (body == null || response.code() == 204) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(
body,
headers
)
}
} else {
val msg = response.errorBody()?.string()
val errorMsg = if (msg.isNullOrEmpty()) {
response.message()
} else {
msg
}
ApiErrorResponse(
errorMsg ?: "Unknown error",
response.code()
)
}
}
}
}
/**
* separate class for HTTP 204 responses so that we can make ApiSuccessResponse's body non-null.
*/
class ApiEmptyResponse<T> : ApiResponse<T>()
data class ApiSuccessResponse<T>(
val body: T?,
val headers: okhttp3.Headers
) : ApiResponse<T>()
data class ApiErrorResponse<T>(val errorMessage: String, val statusCode: Int) : ApiResponse<T>()
Resource:
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
// data class Loading<T>(val loadingData: T?) : Resource<T>(Status.LOADING, loadingData, null)
// data class Success<T>(val successData: T?) : Resource<T>(Status.SUCCESS, successData, null)
// data class Error<T>(val msg: String, val error: T?) : Resource<T>(Status.ERROR, error, msg)
companion object {
fun <T> Success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data,null)
}
fun <T> Error(msg: String, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> Loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
MainViewModel:
class MainViewModel(private val movieRespository: MovieRespository) : ViewModel() {
#ExperimentalCoroutinesApi
val getListMovies: LiveData<Resource<Movie>> = movieRespository.getListMovie().map {
when(it.status){
Resource.Loading() ->{}
Resource.Success() ->{}
Resource.Error() ->{}
}
}.asLiveData(viewModelScope.coroutineContext)
}
to be specific this what the error looks like and this is the link of tutorial i learn
https://github.com/hadiyarajesh/flower
viewModel Class Error
You get the error, because Inside the when, you are trying to construct a new instance of Resource.Loading() etc, but those require a type, so it would need to be something like Resource.Loading<Movie>().
Tht being said, you are doing when(it.status), so the cases in the when, should not be a Resource.Loading, but Status.LOADING instead:
class MainViewModel(private val movieRespository: MovieRespository) : ViewModel() {
#ExperimentalCoroutinesApi
val getListMovies: LiveData<Resource<Movie>> = movieRespository.getListMovie().map {
when(it.status){
Status.LOADING ->{}
Status.SUCCESS ->{}
Status.ERROR ->{}
}
return#map it
}.asLiveData(viewModelScope.coroutineContext)
}
Also, since you are declaring LiveData<Resource<Movie>>, you need to return a Resource<Movie> from the map {} (we could drop return#map, it is just to be explicit)

Why there is red line on myPhone.something1.something() ? -something1-

fun main() {
val myPhone = Myphone()
myPhone.phoneOn()
myPhone.onClick()
myPhone.onTouch()
myPhone.openApp()
myPhone.closeApp()
myPhone.brightMax()
myPhone.Something1.something()
}
interface Application {
var appName: String
var x1: Int
fun something()
fun brightMax() {
println("Brightness level is on Max!")
}
fun openApp() {
println("$appName is opening!")
}
fun phoneOn() {
println("The phone is ON")
}
fun onClick() {
println("App is running")
}
fun closeApp() {
println("${Myphone.Something1.appName} App is closed!")
}
}
interface Button {
val x: Int
var helloMessage: String
fun brightMax() {
println("Brightness is on $x")
}
fun phoneOn() {
println("Power on button was pressed!")
}
fun onClick()
fun onTouch() {
println("The screen was touched!")
}
}
class Myphone: Button, Application {
override fun something() {
println("Doing something")
}
object Something1 : Application {
override var x1: Int = 100
override var appName: String = "Instagram"
override fun something() {
println("He wants to die!")
}
}
override var x1: Int = 12
override var appName: String = "Facebook"
override var x: Int = 100
override fun phoneOn() {
super<Application>.phoneOn()
}
override fun brightMax() {
super<Application>.brightMax()
super<Button>.brightMax()
}
override var helloMessage: String = "Hello"
override fun onClick() {
super.onClick()
}
}
I created object inside the class and I can't "call" it back in main function.
Once I did and I can't remember how to solve it again.
Don't blame me because of code. I made it for presentation.
on the 9th line, there is error, why? the something1 has red line in kotlin.
something1.appName - is working perfectly?
You get the error because it's not recommended to access nested objects via instance references. Use Myphone.Something1.something() instead of myPhone.Something1.something().

Kotlin Coroutine Unit Test Flow collection with viewModelScope

I want to test a method of my ViewModel that collects a Flow. Inside the collector a LiveData object is mutated, which I want to check in the end. This is roughly how the setup looks:
//Outside viewmodel
val f = flow { emit("Test") }.flowOn(Dispatchers.IO)
//Inside viewmodel
val liveData = MutableLiveData<String>()
fun action() {
viewModelScope.launch { privateAction() }
}
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
When I now call the action() method in my unit test, the test finishes before the flow is collected. This is how the test might look:
#Test
fun example() = runBlockingTest {
viewModel.action()
assertEquals(viewModel.liveData.value, "Test")
}
I am using the TestCoroutineDispatcher via this Junit5 extension and also the instant executor extension for LiveData:
class TestCoroutineDispatcherExtension : BeforeEachCallback, AfterEachCallback, ParameterResolver {
#SuppressLint("NewApi") // Only used in unit tests
override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean {
return parameterContext?.parameter?.type === testDispatcher.javaClass
}
override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any {
return testDispatcher
}
private val testDispatcher = TestCoroutineDispatcher()
override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(testDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance()
.setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
You can try either,
fun action() = viewModelScope.launch { privateAction() }
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
#Test
fun example() = runBlockingTest {
viewModel.action().join()
assertEquals(viewModel.liveData.value, "Test")
}
or
fun action() {
viewModelScope.launch { privateAction()
}
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
#Test
fun example() = runBlockingTest {
viewModel.action()
viewModel.viewModelScope.coroutineContext[Job]!!.join()
assertEquals(viewModel.liveData.value, "Test")
}
You could also try this,
suspend fun <T> LiveData<T>.awaitValue(): T? {
return suspendCoroutine { cont ->
val observer = object : Observer<T> {
override fun onChanged(t: T?) {
removeObserver(this)
cont.resume(t)
}
}
observeForever(observer)
}
}
#Test
fun example() = runBlockingTest {
viewModel.action()
assertEquals(viewModel.liveData.awaitValue(), "Test")
}
So what I ended up doing is just passing the Dispatcher to the viewmodel constructor:
class MyViewModel(..., private val dispatcher = Dispatchers.Main)
and then using it like this:
viewModelScope.launch(dispatcher) {}
So now I can override this when I instantiate the ViewModel in my test with a TestCoroutineDispatcher and then advance the time, use testCoroutineDispatcher.runBlockingTest {}, etc.