Add functioning SearchView to one specific Fragment - android

I got a fragment where I want to add a SearchView. I got it already in the Menu, its clickable and everything, but every guide I find to actually search something is either out of date or not exactly what I need.
This is my fragment
class ViewFragment : Fragment () {
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: ViewFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.view_fragment, container, false)
val application = requireNotNull(this.activity).application
setHasOptionsMenu(true)
binding.setLifecycleOwner(this)
viewFragmentViewModel.navigateToEdit.observe(this, Observer {translation ->
translation?.let {
this.findNavController().navigate(
ViewFragmentDirections
.actionViewFragmentToEditFragment(translation)
)
viewFragmentViewModel.onEditNavigated()
}
})
val adapter = TranslationAdapter(TranslationListener { translationID ->
viewFragmentViewModel.onTranslationClicked(translationID)
})
binding.translationList.adapter = adapter
viewFragmentViewModel.translations.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater){
inflater.inflate(R.menu.search_menu, menu)
super.onCreateOptionsMenu(menu, inflater)
}
And this is my menu with the search
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<item
android:id="#+id/action_search"
android:icon="#android:drawable/ic_menu_search"
app:showAsAction="always|collapseActionView"
app:actionViewClass="android.widget.SearchView"
android:title="Search"/>
</menu>
Can anyone tell me how I can get set everything up so I can access the search string the user has entered and submit a new list to the RecycleView? Or just point me in the right direction!
Thank you in advance!
EDIT: Heres The update. I have an MutableLiveData Where i put the query and observe it so I can act on its observe function. I got a variable translationList in the ViewModel that I wanted to update.
class ViewFragment : Fragment (), SearchView.OnQueryTextListener {
private var searchText = MutableLiveData<String>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
...
val adapter = TranslationAdapter(TranslationListener { translationID ->
viewFragmentViewModel.onTranslationClicked(translationID)
})
binding.translationList.adapter = adapter
viewFragmentViewModel.translations.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
searchText.observe(this, Observer {searchString ->
searchString?.let {
if(searchString == ""){
adapter.submitList(viewFragmentViewModel.translations.value)
}
else{
adapter.submitList(viewFragmentViewModel.searchTranslations(searchString))
}
}
})
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater){
inflater.inflate(R.menu.search_menu, menu)
val menuItem = menu.findItem(R.id.action_search)
val searchView = menuItem.actionView as SearchView
searchView.setOnQueryTextListener(this)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onQueryTextChange(newText: String?): Boolean {
searchText.value = newText
return true
}
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
}
For the closure, since I already had an list with all my objects from the database I wanted to search in, its easier to just go through that list with a filter.

Step - 1: Implements SearchView.OnQueryTextListener in your ViewFragment
class ViewFragment : Fragment(), SearchView.OnQueryTextListener {
...
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
//Do your operation here
return true
}
}
Step - 2: Get SearchView from menu and initialize with listener
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.search_menu, menu)
val meniItem = menu?.findItem(R.id.action_search)
val searchView = meniItem?.actionView as SearchView?
searchView?.setOnQueryTextListener(this)
super.onCreateOptionsMenu(menu, inflater)
}
Step - 3: Inside onQueryTextChange take your search query and do operation
Update: Change your implementation like below:
fun searchTranslations(searchString: String) : List<Translation>? {
return database.getTranslationsBySearch(searchString).value
}
And Dao
#Query("SELECT * FROM word_translation_table WHERE word_ger LIKE '% :string %' OR word_es LIKE '% :string %' ")
fun getTranslationsBySearch(string: String): LiveData<List<Translation>>

Related

Fragment not attached to an activity when send data from my frgament to Viewmodel

I have a problem when sending data from my fragment to the ViewModel that says:
java.lang.IllegalStateException fragment not attached to an activity, Shutting down VM , Fatal Exception
val viewmodel :Store1_viewmodel by activityViewModels()
private lateinit var binding: FragmentShakaStoreBinding
lateinit var dataset :MutableList<item1>
lateinit var recycler :RecyclerView
lateinit var tempdataset:MutableList<item1>
lateinit var adapter: shaka_recycler_Adapter
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentShakaStoreBinding.inflate(inflater,container,false)
recycler =binding.shakaRecycler
// dataset=viewmodel.get()
dataset= mutableListOf()
val curdate =LocalDate.now().format(DateTimeFormatter.ofPattern("dd-MM-yyyy"))
lifecycleScope.launch {
val db = activity?.let { Store1_Database2.getdatabase(it?.applicationContext) }
db?.itemdao()?.getAll()?.let { dataset.addAll(it) }
adapter =shaka_recycler_Adapter(Shaka_Store(),dataset,Shaka_Store())
onclick(4)
recycler.adapter=adapter
}
recycler.addItemDecoration(DividerItemDecoration(
context,LinearLayoutManager.HORIZONTAL
))
recycler.setHasFixedSize(true)
setHasOptionsMenu(true)
return binding.root
// return inflater.inflate(R.layout.fragment_shaka__store, container, false)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.add_item,menu)
var menuittem =menu!!.findItem(R.id.search)
val searchview =menuittem.actionView as SearchView
searchview.maxWidth = Int.MAX_VALUE
searchview.setOnQueryTextListener(object :SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(p0: String?): Boolean {
// adapter.filter.filter(p0)
return true
}
override fun onQueryTextChange(p0: String?): Boolean {
adapter!!.filter.filter(p0)
Log.v("worrd",p0.toString())
return true
}
})
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.add_item->{
findNavController().navigate(R.id.action_shaka_Store_to_insert_item)
}
}
return super.onOptionsItemSelected(item)
}
override fun onclick(Name: Int) {
viewmodel.setitemname(Name)
}
}
and this is my viewmodel code
val context = application
private val _clicked = MutableLiveData<Int>()
val clicked :LiveData<Int> = _clicked
fun insert (data :item1){
viewModelScope.launch {
val db =Store1_Database2.getdatabase(context)
db.itemdao().insert_item(data)
Toast.makeText(context,"تم اضافة العنصر بنجاح ",Toast.LENGTH_LONG).show()
}
}
fun setitemname(name:Int){
_clicked.value = name
}
}
I am using a coroutines is this the reason why I can not send data from fragment to viewmodel ?

How to add QueryTextListener for searchView in android fragment?

I have an in app contacts list screen (which is a fragment) and I want to add a searchView in my toolbar. This is my code:
Fragment code:
private lateinit var contactsToolbar: Toolbar
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_contacts, container, false)
setHasOptionsMenu(true);
contactsToolbar = rootView.findViewById(R.id.contacts_toolbar)
contactsToolbar.inflateMenu(R.menu.contacts_menu)
contactsToolbar.title = "Contacts"
.......
return rootView
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.contacts_menu, menu)
val search = menu.findItem(R.id.action_search)
val searchView = search.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
Log.d(TAG, "new query $query")
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
Log.d(TAG, "new text $newText")
return true
}
})
super.onCreateOptionsMenu(menu, inflater)
}
and this is my menu
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/action_search"
android:icon="#android:drawable/ic_menu_search"
app:showAsAction="always|collapseActionView"
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:title="Search"/>
</menu>
I can see that the searchView is displayed in the fragmanet as expected but the queryTextListener never gets called. Does anyone have any idea why this is happening?
Thank you in advance!
May be you can init the SearchView after onCreateOptionsMenu method?
I cannot sure it is true, but I met the same question as you, the text listener doesn't work.
because I init my SearchView like
val mySearchView by lazy{
...
}
Found a solution! Not sure if is the most elegant one but might help somebody in the future. Here is the fix:
In the onCreateView add this line of code:
yourActivity().setSupportActionBar(contactsToolbar)
This will make the searchView listener work but it might display the menu from the activity if you have one. To prevent this from happening and this line in onCreateOptionsMenu before inflating your menu.
menu.clear()

Show API response on fragment on submit

When I search on my searchview I want to send the search value to the api that will give back a response that I want to be showed on the fragment. So when I submit my search I want to show the fragment with the response! I tried to make a function to render the fragment but I think im doing it completly wrong...
Im begginer and this is for a project for school, thank you for help!
SearchView
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
val manager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem?.actionView as SearchView
searchView.setSearchableInfo(manager.getSearchableInfo(componentName))
searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
searchView.clearFocus()
searchView.setQuery("",false)
searchItem.collapseActionView()
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return false
}
})
return true
}
Data Class
data class SearchPost(val searchKey: String)
Fragment
class SendFragment : Fragment() {
var newList: MutableList<News> = mutableListOf<News>()
companion object {
fun newInstance() = SendFragment()
}
private lateinit var viewModel: SendViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_searched, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(SendViewModel::class.java)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this.context)
// TODO: Use the ViewModel
val searchedObserver = Observer<List<News>>
{
// Access the RecyclerView Adapter and load the data into it
newList -> recyclerView.adapter = NewsAdapter(newList,this.context!!)
}
viewModel.getNewSearched().observe(this, searchedObserver)
}
}
Fragment View Model
class SendViewModel : ViewModel() {
// TODO: Implement the ViewModel
private var newList: MutableLiveData<List<News>> = MutableLiveData()
fun getNewSearched(): MutableLiveData<List<News>>
{
searchedNew()
return newList;
}
private fun searchedNew()
{
val retrofit = Retrofit.Builder()
.baseUrl("http://192.168.1.78:3000")
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(ApiService::class.java)
val searchPost = SearchPost("this is want to be the query")
api.sendSearch(searchPost).enqueue(object : Callback<List<News>> {
override fun onResponse(call: Call<List<News>>, response: Response<List<News>>) {
newList.value=(response.body()!!)
}
override fun onFailure(call: Call<List<News>>, t: Throwable) {
Log.d("fail", "onFailure:")
}
})
}
}
Api interface
interface ApiService {
#POST("/search")
fun sendSearch(#Body searchPost: SearchPost): Call<List<News>>
}
Observe view model like this :
viewModel.getNewSearched().observe(this, Observer<MutableList<List<News>>> {
myNewsData ->
Log.d("print my data", myNewsData) // first try to print this data whether data is coming or not
recyclerView.adapter = NewsAdapter( myNewsData ,this.context!!)
})
Initialize your searchView :
private fun initSearchView() {
search_view.setOnQueryTextListener(object :
androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(newText: String?): Boolean {
viewMode.getNewSearched() // call your api here
return false // don,t change it to true
}
override fun onQueryTextChange(query: String?): Boolean {
return false
}
})
}
At last I thing you should pass the text to viewmodel which you want to search
override fun onQueryTextSubmit(query: String?): Boolean {
viewMode.getNewSearched(query)
In side viewmodel :
fun getNewSearched(textYouWantToSearch :String): MutableLiveData<List<News>>
{
searchedNew(textYouWantToSearch) // same pass in searchedNew() else your data class is always blank
return newList;
}
In my understanding your data class which you are passing in your retrofit call is always blank because you are not passing any value from anywhere you should use query string of your search method and try to pass as an parameter.

Show menu actions only in certain fragments - NavigationComponents

I have a navigation host activity that inflates 2 fragments, one the main host fragment and the event fragments, I need to show in the event fragment a menu action , but this menu action is also shown in the main fragment which I dont want to show
How do I show this menu only in the event fragment ?
MainActivity
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
setupActionBar(navController)
}
private fun setupActionBar(navController: NavController) {
NavigationUI.setupActionBarWithNavController(this, navController)
}
override fun onSupportNavigateUp(): Boolean {
return Navigation.findNavController(this, R.id.nav_host_fragment).navigateUp()
|| super.onSupportNavigateUp()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
MenuInflater(this).inflate(R.menu.menu, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
NavigationUI.onNavDestinationSelected(item!!, Navigation.findNavController(this, R.id.nav_host_fragment))
return super.onOptionsItemSelected(item)
}
Menu
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/eventsFragment"
app:showAsAction="ifRoom|always"
android:title="¿ Where to buy ?" />
</menu>
I need to show this menu only in the eventsFragment
EventFragment
class EventsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_event, container, false)
}
But instead of just showing it just in the EventFragment it also shows it in the first fragment that MainActivity as hosts inflates
How can I just have this menu in my EventsFragment?
In MainActivity menuItem setVisible(false):
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
MenuInflater(this).inflate(R.menu.menu, menu)
val menuItem : MenuItem = menu.findItem(R.id.eventsFragment);
if (menuItem!= null)
menuItem.setVisible(false);
return super.onCreateOptionsMenu(menu)
}
In EventsFragment setvisibility of menuitem to true as shown below:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onPrepareOptionsMenu(menu: Menu) {
val menuItem = menu.findItem(R.id.eventsFragment)
if (menuItem != null)
menuItem.isVisible = true
}
You could try considering Inflating the menu from inside the fragment. ie. fragments onCreate() method and destroying it from onDestroy() function of the fragment

Clear Searchview Text on navigation.

I have an Activity with two child fragments Timeline and Milestones. Both these fragments contain listviews populated by a custom Cursor adapter
Here is a graphical Representation:
Now when I am on TIMELINE and I open up the searchview, I type something all is good I get the desired result. But when I navigate from Timeline to Milestones with some text in the searchview the searchview does not get cleared, so I get filtered results on the Milestones page too and acccording to the paramaters I provided in Timeline.
I am using AppCompact lib to develop my ActionBar. The tabs in there are not ActionBar Tabs but simple SlidingTabLayout.
So far I have tried using
getActivity().supportInvalidateOptionsMenu(); in onResume() of both the fragments, does not work.
I have tried searchView.setQuery("",false) - does not work and randomly gives me a NPE.
SO what do I miss here?
You can take a look on my example, where I showed how to control searchView between fragments.
Firstly. You need to create BaseFragment, which works with context of activity with appBarLayout.
open class BaseFragment: Fragment() {
lateinit var rootActivity: MainActivity
lateinit var appBarLayout: AppBarLayout
lateinit var searchView: androidx.appcompat.widget.SearchView
override fun onAttach(context: Context) {
super.onAttach(context)
this.rootActivity = context as MainActivity
appBarLayout = rootActivity.findViewById(R.id.app_bar_layout)
searchView = rootActivity.findViewById(R.id.search_input)
}
override fun onResume() {
super.onResume()
resetAppBarLayout()
}
private fun resetAppBarLayout() {
appBarLayout.elevation = 14f
}
fun setupSearch(query: String) {
searchView.visibility = View.VISIBLE
searchView.clearFocus()
when(query.isNotEmpty()) {
true -> {
searchView.setQuery(query, true)
searchView.isIconified = false
}
false -> {
searchView.isIconified = true
searchView.isIconified = true
}
}
}
fun hideSearchKeyboard() {
context?.let {
KeyboardHelper.hideSearchKeyboard(it, searchView.findViewById(R.id.search_src_text))
}
}
fun hideSearch() {
searchView.visibility = View.GONE
searchView.clearFocus()
}
}
Secondly. Inherit your fragments from BaseFragment, override onResume() method and control searchView in your fragments by calling methods from BaseFragment. Something like this.
class FragmentA : BaseFragment() {
private var searchQuery = ""
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment, container, false)
val textView: TextView = root.findViewById(R.id.textView)
textView.text = "Fragment A"
return root
}
override fun onResume() {
super.onResume()
setupSearch()
}
private fun setupSearch() {
searchView.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String?): Boolean {
when(newText.isNullOrEmpty()) {
true -> searchQuery = ""
false -> searchQuery = newText
}
return true
}
override fun onQueryTextSubmit(query: String?): Boolean {
hideSearchKeyboard()
return true
}
})
super.setupSearch(searchQuery)
}
}
Full example you can find here https://github.com/yellow-cap/android-manage-searchview

Categories

Resources