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.
Related
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 ?
I have an activity that has a SearchView that I use to enter a query, my app then uses to query to access an API. My activity further contains a fragment, and within this fragment I have my observer.
Further I have my ViewModel, which makes the API call when given a query. However, my observer is never notified about the update, and thus my view never updates. Unless I call it directly from my ViewModel upon initiation. I'll show it specifically here:
ViewModel
class SearchViewModel : ViewModel() {
val booksResponse = MutableLiveData<MutableList<BookResponse>>()
val loading = MutableLiveData<Boolean>()
val error = MutableLiveData<String>()
init {
getBooks("How to talk to a widower")
}
fun getBooks(bookTitle: String) {
GoogleBooksService.api.getBooks(bookTitle).enqueue(object: Callback<ResponseWrapper<BookResponse>> {
override fun onFailure(call: Call<ResponseWrapper<BookResponse>>, t: Throwable) {
onError(t.localizedMessage)
}
override fun onResponse(
call: Call<ResponseWrapper<BookResponse>>,
response: Response<ResponseWrapper<BookResponse>>
) {
if (response.isSuccessful){
val books = response.body()
Log.w("2.0 getFeed > ", Gson().toJson(response.body()));
books?.let {
// booksList.add(books.items)
booksResponse.value = books.items
loading.value = false
error.value = null
Log.i("Content of livedata", booksResponse.getValue().toString())
}
}
}
})
}
private fun onError(message: String) {
error.value = message
loading.value = false
}
}
Query Submit/ Activity
class NavigationActivity : AppCompatActivity(), SearchView.OnQueryTextListener, BooksListFragment.TouchActionDelegate {
lateinit var searchView: SearchView
lateinit var viewModel: SearchViewModel
private val mOnNavigationItemSelectedListener =
BottomNavigationView.OnNavigationItemSelectedListener { menuItem ->
when (menuItem.itemId) {R.id.navigation_search -> {
navigationView.getMenu().setGroupCheckable(0, true, true);
replaceFragment(SearchListFragment.newInstance())
return#OnNavigationItemSelectedListener true
}
R.id.navigation_books -> {
navigationView.getMenu().setGroupCheckable(0, true, true);
replaceFragment(BooksListFragment.newInstance())
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
replaceFragment(SearchListFragment.newInstance())
navigationView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
//Set action bar color
val actionBar: ActionBar?
actionBar = supportActionBar
val colorDrawable = ColorDrawable(Color.parseColor("#FFDAEBE9"))
// actionBar!!.setBackgroundDrawable(colorDrawable)
// actionBar.setTitle(("Bobs Books"))
setSupportActionBar(findViewById(R.id.my_toolbar))
viewModel = ViewModelProvider(this).get(SearchViewModel::class.java)
}
override fun onBackPressed() {
super.onBackPressed()
navigationView.getMenu().setGroupCheckable(0, true, true);
}
private fun replaceFragment(fragment: Fragment){
supportFragmentManager
.beginTransaction()
.replace(R.id.fragmentHolder, fragment)
.commit()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.book_search_menu, menu)
val searchItem = menu.findItem(R.id.action_search)
searchView = searchItem.actionView as SearchView
searchView.setOnQueryTextListener(this)
searchView.queryHint = "Search for book"
/*searchView.onActionViewExpanded()
searchView.clearFocus()*/
// searchView.setIconifiedByDefault(false)
return true
}
override fun onQueryTextSubmit(query: String): Boolean {
//replaces fragment if in BooksListFragment when searching
replaceFragment(SearchListFragment.newInstance())
val toast = Toast.makeText(
applicationContext,
query,
Toast.LENGTH_SHORT
)
toast.show()
searchView.setQuery("",false)
searchView.queryHint = "Search for book"
// viewModel.onAddBook(Book(title = query!!, rating = 5, pages = 329))
Log.i("Query fra text field", query)
// viewModel.getBooks(query)
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
return false
}
override fun launchBookFragment(bookId: Book) {
supportFragmentManager
.beginTransaction()
.replace(R.id.fragmentHolder, com.example.bobsbooks.create.BookFragment.newInstance(bookId.uid))
.addToBackStack(null)
.commit()
navigationView.getMenu().setGroupCheckable(0, false, true);
}
}
Fragment
class SearchListFragment : Fragment() {
lateinit var viewModel: SearchViewModel
lateinit var contentListView: SearchListView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_search_list, container, false).apply {
contentListView = this as SearchListView
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindViewModel()
setContentView()
}
private fun setContentView(){
contentListView.initView()
}
private fun bindViewModel(){
Log.i("ViewmodelCalled", "BindViewModel has been called")
viewModel = ViewModelProvider(this).get(SearchViewModel::class.java)
viewModel.booksResponse.observe(viewLifecycleOwner, Observer {list ->
list?.let {
Log.i("Observer gets called", "Updatelistgetscalled")
contentListView.updateList(list)
}
} )
viewModel.error.observe(viewLifecycleOwner, Observer { errorMsg ->
})
viewModel.loading.observe(viewLifecycleOwner, Observer { isLoading ->
})
}
companion object {
fun newInstance(): SearchListFragment {
return SearchListFragment()
}
}
When I put the getBooks call into my Viewmodel Init, it will do everything correctly. It gets the bookresponse through the API, adds it to my LiveData and notifies my adapter.
However, if I instead delete that and call it through my Querysubmit in my Activity, it will, according to my logs, get the data and put it into my booksReponse:LiveData, but thats all it does. The observer is never notifed of this change, and thus the adapter never knows that it has new data to populate its views.
I feel like I've tried everything, I even have basically the same code working in another app, where it runs entirely in an activity instead of making the query in an activity, and rest is called in my fragment. My best guess is this has an impact, but I cant figure out how.
As per your explanation
However, if I instead delete that and call it through my Querysubmit in my Activity, it will, according to my logs, get the data and put it into my booksReponse:LiveData, but thats all it does. The observer is never notifed of this change, and thus the adapter never knows that it has new data to populate its views.
the problem is you are initializing SearchViewModel in both activity & fragment, so fragment doesn't have the same instance of SearchViewModel instead you should use shared viewmodel in fragment like :
viewModel = ViewModelProvider(requireActivity()).get(SearchViewModel::class.java)
I'm attempting to set my search box to not return any search results when the query is empty - i.e. when nothing has been typed in the box. Algolia InstantSearch by default returns all entries to scroll through which are then filtered as the user searches.
I followed the API docs on aloglia's website for removing the empty query but my search box still returns all entries. I'm a little stuck since it seems to be a very straightforward class, but using the default SearchBoxView vs my amended version NoEmptySearchBox makes no difference in results.
Here's GroupFragment where I'm calling the SearchBoxView:
class GroupFragment : Fragment() {
private val connection = ConnectionHandler()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.group_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewModel = ViewModelProviders.of(requireActivity())[SearcherViewModel::class.java]
val searchBoxView = NoEmptySearchBox(searchView)
viewModel.groups.observe(this, Observer { hits -> viewModel.adapterProduct.submitList(hits) })
connection += viewModel.searchBox.connectView(searchBoxView)
groupList.let {
it.itemAnimator = null
it.adapter = viewModel.adapterProduct
it.layoutManager = LinearLayoutManager(requireContext())
it.autoScrollToStart(viewModel.adapterProduct)
}
}
override fun onDestroyView() {
super.onDestroyView()
connection.disconnect()
}
}
And here's my NoEmptySearchBox class, which implements SearchBoxView:
class NoEmptySearchBox (
val searchView: SearchView
) : SearchBoxView {
override var onQueryChanged: Callback<String?>? = null
override var onQuerySubmitted: Callback<String?>? = null
init {
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
query?.isNotEmpty()?.let { onQuerySubmitted?.invoke(query) }
return false
}
override fun onQueryTextChange(query: String?): Boolean {
query?.isNotEmpty()?.let { onQuerySubmitted?.invoke(query) }
return false
}
})
}
override fun setText(text: String?, submitQuery: Boolean) {
searchView.setQuery(text, false)
}
}
And here's my SearcherViewModel:
class SearcherViewModel : ViewModel() {
val client = ClientSearch(ApplicationID("APP_ID"), APIKey("API_KEY"), LogLevel.ALL)
val index = client.initIndex(IndexName("groups"))
val searcher = SearcherSingleIndex(index)
override fun onCleared() {
super.onCleared()
searcher.cancel()
connection.disconnect()
}
val dataSourceFactory = SearcherSingleIndexDataSource.Factory(searcher) { hit ->
Group(
hit.json.getPrimitive("course_name").content,
hit.json.getObjectOrNull("_highlightResult")
)
}
val pagedListConfig = PagedList.Config.Builder().setPageSize(50).build()
val groups: LiveData<PagedList<Group>> = LivePagedListBuilder(dataSourceFactory, pagedListConfig).build()
val adapterProduct = GroupAdapter()
val searchBox = SearchBoxConnectorPagedList(searcher, listOf(groups))
val connection = ConnectionHandler()
init {
connection += searchBox
}
}
I have an activity which has a RecyclerView I'd like to filter using a SearchView I set up.
class ExerciseCatalogueActivity : AppCompatActivity(), ExerciseClickedListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_exercise_catalogue_activity)
val toolbar = toolbar_catalogue
setSupportActionBar(toolbar)
val exercisesCatalogueList : MutableList<Exercise>
val exerciseCatalogueFullList: MutableList<Exercise>
val exerciseFileString: String = resources.openRawResource(R.raw.exercises1).bufferedReader().use { it.readText() }
exercisesCatalogueList = (Gson().fromJson(exerciseFileString, Array<Exercise>::class.java)).toMutableList()
exerciseCatalogueFullList = ArrayList<Exercise>(exercisesCatalogueList)
rv_catalogue.apply {
rv_catalogue.layoutManager = LinearLayoutManager(context, LinearLayout.VERTICAL, false)
adapter = ExerciseCatalogueRecyclerViewAdapter(exercisesCatalogueList,exerciseCatalogueFullList, this#ExerciseCatalogueActivity)
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar_menu, menu)
val searchItem = menu!!.findItem(R.id.action_search_bar)
val searchView = searchItem.actionView as SearchView
searchView.imeOptions = EditorInfo.IME_ACTION_DONE
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
adapter.filter.filter(newText)
return false
}
})
return true
}
}
However adapter.filter.filter(newText) returns as unresolved and I cannot filter my recyclerView. What changes need to be made?
You haven't saved a reference to your adapter.
You use .apply on the RecyclerView called rv_catalogue, so that works inside the scope.
In onCreateOptionsMenu, there is no adapter to reference.
Either save a reference to the adapter in your Activity, or use rv_catalogue.adapter to get a reference to the adapter.
So I have a ViewModel that retrieve query for search API. For that, I also have SearchView but when typing the first letter on SearchView the app crashed because KotlinNullPointer on this line inside retrofit
resultsItem?.value = resultsItemList as List<ResultsItem>?
I think I have done everything right, I tried
Creating own method to pass data to ViewModel
Using intent to pass data to ViewModel
Defining default value inside ViewModel which works, but can't change after defined
Here is the code for the Fragment
class Search : Fragment() {
var searchAdapter: SearchAdapter? = null
lateinit var recyclerView: RecyclerView
lateinit var model: picodiploma.dicoding.database.picodiploma.dicoding.database.search.adapter.SearchView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_search, container, false)
recyclerView = view.findViewById(R.id.search_result_tv)
val layoutManager = LinearLayoutManager(context)
recyclerView.layoutManager = layoutManager
model = ViewModelProviders.of(this).get(picodiploma.dicoding.database.picodiploma.dicoding.database.search.adapter.SearchView::class.java)
return view
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.search, menu)
val searchItem = menu.findItem(R.id.search_)
val searchView = searchItem?.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(s: String): Boolean {
return false
}
override fun onQueryTextChange(s: String): Boolean {
model.query = s
getViewData()
return true
}
})
}
fun getViewData() {
model.getData().observe(this, Observer { resultsItem ->
searchAdapter = SearchAdapter((resultsItem as ArrayList<ResultsItem>?)!!, this.context!!)
recyclerView.adapter = searchAdapter
recyclerView.visibility = View.VISIBLE
})
}
}
And the ViewModel
class SearchView : ViewModel() {
private val API_KEY = "2e08750083b7e21e96e915011d3f8e2d"
private val TAG = SearchView::class.java.simpleName
lateinit var query: String
companion object {
var resultsItem: MutableLiveData<List<ResultsItem>>? = null
}
fun getData(): LiveData<List<ResultsItem>> {
if (resultsItem == null) {
resultsItem = MutableLiveData()
loadData()
}
return resultsItem as MutableLiveData<List<ResultsItem>>
}
private fun loadData() {
val apiInterface = ApiClient.getList().create(ApiInterface::class.java)
val responseCall = apiInterface.getTvSearch(API_KEY, query)
responseCall.enqueue(object : Callback<Response> {
override fun onResponse(call: Call<Response>, response: retrofit2.Response<Response>) {
val resultsItemList = response.body()!!.results
resultsItem?.value = resultsItemList as List<ResultsItem>?
}
override fun onFailure(call: Call<Response>, t: Throwable) {
Log.d(TAG, t.toString())
}
})
}
}
What am I doing wrong?
Seems like you defined resultsItem as nullable MutableLiveData, but the List<ResultsItem> inside your LiveData is not nullable.
So I guess your resultsItemList is null when you get response from the server. And you are getting KotlinNullPointer because you are trying to assign null to notNull value of resultsItem LiveData.
Change below line
var resultsItem: MutableLiveData<List<ResultsItem>>? = null
to
var resultsItem: MutableLiveData<List<ResultsItem>>? = MutableLiveData()
Put everything inside apply
run{
searchAdapter = SearchAdapter((resultsItem as ArrayList<ResultsItem>?)!!, this.context!!)
recyclerView.adapter = searchAdapter
recyclerView.visibility = View.VISIBLE
}