I was trying to create a search function for listview, and putting the intent activity for the results. However, as the search results appear, the position changed. For example, original position should be 0,1. But after searched, the results turn the position to 3,4. I tried finding many solutions for this, but nothing helps. Thanks in advance !
Here the homefragment.java
package com.example.testtt;
import androidx.cardview.widget.CardView;
import androidx.fragment.app.Fragment;;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.SearchView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
public class HomeFragment extends Fragment implements View.OnClickListener {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_home, container, false);
//define
RelativeLayout drawerlayouthome;
ListView list;
ListViewAdapter adapter;
SearchView editsearch;
ArrayList<DoaSearchIndex> arrayList = new ArrayList<DoaSearchIndex>();
String[] doasearchindex = getActivity().getResources().getStringArray(R.array.doasearchindex);
//locatelistview
list = v.findViewById(R.id.searchlistview);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Intent intent;
switch (i){
case 0:
intent = new Intent(getActivity(), firstactivity.class);
startActivity(intent);
getId();
break;
case 1:
intent = new Intent(getActivity(), secondactivity.class);
startActivity(intent);
getId();
break;
}
list.setVisibility(View.INVISIBLE);
}
});
for (int i = 0; i < doasearchindex.length; i++) {
DoaSearchIndex doaSearchIndex = new DoaSearchIndex(doasearchindex[i]);
arrayList.add(doaSearchIndex);
}
//pass result to listviewadapter.class
adapter = new ListViewAdapter(this.getActivity(), arrayList);
//binds adapter to listview
list.setAdapter(adapter);
list.setVisibility(View.INVISIBLE);
//locatesearchview
editsearch = v.findViewById(R.id.searchview);
editsearch.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
if(TextUtils.isEmpty(newText)){
list.clearTextFilter();
adapter.filter("");
list.setVisibility(View.INVISIBLE);
} else {
adapter.filter(newText);
list.setVisibility(View.VISIBLE);
}
return true;
}
});
drawerlayouthome = v.findViewById(R.id.drawer_layouthome);
drawerlayouthome.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
list.setVisibility(View.INVISIBLE);
editsearch.clearFocus();
}
});
//definingcardview
CardView test = v.findViewById(R.id.test);
CardView testcard = v.findViewById(R.id.testcard);
//setcardviewlistener
test.setOnClickListener(this);
testcard.setOnClickListener(this);
return v;
}
@Override
public void onClick(View view) {
Intent i ;
switch (view.getId()) {
case R.id.test:
i = new Intent(getActivity(), test.class);
startActivity(i);
break;
case R.id.testcard:
i = new Intent(getActivity(), testcard.class);
startActivity(i);
break;
}
}
}
and ListViewAdapter.java
package com.example.testtt;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class ListViewAdapter extends BaseAdapter {
//Declare variables first
Context mContext;
LayoutInflater inflater;
private List<DoaSearchIndex> doaSearchIndexList = null;
private ArrayList<DoaSearchIndex> arrayList;
public ListViewAdapter(Context context, List<DoaSearchIndex> doaSearchIndexList) {
mContext = context;
this.doaSearchIndexList = doaSearchIndexList;
inflater = LayoutInflater.from(mContext);
this.arrayList = new ArrayList<DoaSearchIndex>();
this.arrayList.addAll(doaSearchIndexList);
}
public class ViewHolder {
TextView name;
}
@Override
public int getCount() {
return doaSearchIndexList.size();
}
@Override
public DoaSearchIndex getItem(int position) {
return doaSearchIndexList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
public View getView(final int position, View view, ViewGroup parent) {
final ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
view = inflater.inflate(R.layout.listview_item, null);
// Locate the TextViews in listview_item.xml
holder.name = (TextView) view.findViewById(R.id.name);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
// Set the results into TextViews
holder.name.setText(doaSearchIndexList.get(position).getDoaSearchIndex());
return view;
}
// Filter Class
public void filter(String charText) {
charText = charText.toLowerCase(Locale.getDefault());
doaSearchIndexList.clear();
if (charText.length() == 0) {
doaSearchIndexList.addAll(arrayList);
} else {
for (DoaSearchIndex wp : arrayList) {
if (wp.getDoaSearchIndex().toLowerCase(Locale.getDefault()).contains(charText)) {
doaSearchIndexList.add(wp);
}
}
}
notifyDataSetChanged();
}
}
and the DoaSearchIndex.java
public class DoaSearchIndex {
private String doaSearchIndex;
private int originalPosition;
public DoaSearchIndex(String doaSearchIndex) {
this.doaSearchIndex = doaSearchIndex;
}
public String getDoaSearchIndex() {
return this.doaSearchIndex;
}
private int getPosition() {
return this.originalPosition;
}
}
Here is my example Fragment
:
public class MyDefaultFragment extends Fragment {
//Default items
public static ArrayList<MyItem> myItems = new ArrayList<>();
//Search filter
public static String filter = "";
public MyDefaultFragment(){
//require a empty constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.defaultfragment,container,false);
//I used a EditText instead of a SearchView
EditText editText = view.findViewById(R.id.filterEditText);
RecyclerView recyclerView = view.findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
recyclerView.setHasFixedSize(true);
//Checks for text change
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
filter = editText.getText().toString();
handler.postDelayed(this,500);
}
},500);
//Updates the Database every second | 1000 millisecond = 1 second
Handler update = new Handler();
update.postDelayed(new Runnable() {
@Override
public void run() {
new MyDatabase.AsyncTask(MyDatabase.getInstance(getContext())).execute();
update.postDelayed(this,1000);
}
},1000);
//Creates a new object in the Adapter.class
Adapter adapter = new Adapter();
myItems.clear();
//example items
myItems.add(new MyItem(R.drawable.bcn100, "firstActivity"));
myItems.add(new MyItem(R.drawable.ada100, "secondActivity"));
myItems.add(new MyItem(R.drawable.cos100, "otherActivity"));
recyclerView.setAdapter(adapter);
//Updates the Database on open the Fragment
new MyDatabase.AsyncTask(MyDatabase.getInstance(getContext())).execute();
//observe the LiveData<List<MyItem>> to an List<MyItem>
MyItemViewModel myItemViewModel = new ViewModelProvider(this).get(MyItemViewModel.class);
myItemViewModel.getAllItems().observe(getViewLifecycleOwner(), adapter::setItems);
return view;
}
}
Here is the Adapter
for the CardView
:
public class Adapter extends RecyclerView.Adapter<Adapter.CardHolder> {
//Item list of the Adapter
public static List<MyItem> items = new ArrayList<>();
@NonNull
@Override
public CardHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//CardView the name of the layout file smallcard
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.smallcard, parent, false);
CardHolder smallCardHolder = new CardHolder(itemView);
return smallCardHolder;
}
@Override
public void onBindViewHolder(@NonNull final CardHolder holder, int position) {
//get the item of the position
MyItem myItem = items.get(position);
int image = myItem.getMyImage();
String name = myItem.getMyName();
holder.imageView.setImageResource(image);
holder.textViewName.setText(name);
//onClickListener for the cardView
if(holder.cardView!= null) {
holder.cardView.setOnClickListener(v -> {
openActivity(holder.textViewName.getText().toString());
});
}
}
//You have to put here the method to open the activitys
public void openActivity(String name){
switch (name){
case "firstActivity":{
System.out.println("Open firstActivity");
break;
} case "secondActivity":{
System.out.println("Open secondActivity");
break;
} case "otherActivity":{
System.out.println("Open otherActivity");
break;
}
default:
break;
}
}
//Amount of items
@Override
public int getItemCount () {
return items.size();
}
public void setItems(List <MyItem> items) {
this.items = items;
notifyDataSetChanged();
}
//Card Holder
class CardHolder extends RecyclerView.ViewHolder {
private CardView cardView;
private ImageView imageView;
private TextView textViewName;
public CardHolder(@NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.card_View);
imageView = itemView.findViewById(R.id.card_Image);
textViewName = itemView.findViewById(R.id.card_Name);
}
}
}
Here's the Database
to insert/delete the CardView
//If you change the database schema you have to update the current version +1
@Database(entities = {MyItem.class},version = 1)
public abstract class MyDatabase extends RoomDatabase {
private static MyDatabase instance;
public abstract MyItemDao myItemDao();
public static synchronized MyDatabase getInstance(Context context){
//if the instance is null
if(instance == null){
instance = Room.databaseBuilder(context,MyDatabase.class,"my_Database")
.fallbackToDestructiveMigration()
.addCallback(callback)
.setAutoCloseTimeout(1, TimeUnit.SECONDS)
.build();
}
return instance;
}
private static Callback callback = new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
new AsyncTask(instance).execute();
}
};
// The Async Task is deprecated you can use the Kotlin CoroutineAsyncTask instead of android.os.AsyncTask
// If you want to change it, this will help you
// https://stackoverflow.com/questions/68136577/how-to-replace-asynctask-in-this-android-code/68137304#68137304
public static class AsyncTask extends android.os.AsyncTask<Void,Void,Void>{
MyItemDao myItemDao;
public AsyncTask(MyDatabase myDatabase){
this.myItemDao = myDatabase.myItemDao();
}
@Override
protected Void doInBackground(Void... voids) {
//default Items
List<MyItem> allDefaultItems = MyDefaultFragment.myItems;
//Current listed items in the RecyclerView
List<MyItem> allAdapterItems = Adapter.items;
String filter = MyDefaultFragment.filter;
int length = filter.length();
//Search method
if(length >0){
List<MyItem> deleteItems = new ArrayList<>();
List<MyItem> updateItems = new ArrayList<>();
allDefaultItems.forEach(myItem -> {
if(myItem.myName.toLowerCase().startsWith(filter)){
updateItems.add(myItem);
}else{
deleteItems.add(myItem);
}
});
myItemDao.delete(deleteItems);
myItemDao.insertAll(updateItems);
}else{
myItemDao.insertAll(allDefaultItems);
}
return null;
}
}
}
MyItem:
//If you want can change the tableName you have also change the name in the MyItemDao interface
@Entity(tableName = "myTable")
public class MyItem {
int myImage;
//A PrimaryKey is require for a Database
@PrimaryKey
@NonNull
String myName;
public MyItem(int myImage,String myName){
this.myImage = myImage;
this.myName = myName;
}
public int getMyImage() {
return myImage;
}
public void setMyImage(int myImage) {
this.myImage = myImage;
}
public String getMyName() {
return myName;
}
public void setMyName(String myName) {
this.myName = myName;
}
}
MyItemDao.java
:
@Dao
public interface MyItemDao {
//Select all data from myTable
@Query("Select * From myTable")
LiveData<List<MyItem>> getAllItems();
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<MyItem> myItems);
@Delete
void delete(List<MyItem> myItems);
@Query("Delete from myTable")
void deleteAllItems();
}
Repository:
public class MyItemRepository {
private LiveData<List<MyItem>> allItems;
private MyItemDao myItemDao;
public MyItemRepository(Application application){
MyDatabase myDatabase = MyDatabase.getInstance(application);
myItemDao = myDatabase.myItemDao();
allItems = myItemDao.getAllItems();
}
public void insertAll(List<MyItem> yItems){ new InsertAllItemsAsyncTask(myItemDao).execute(myItems); }
public void deleteItems(List<MyItem> myItems){
new DeleteItemListAsyncTask(myItemDao).execute(myItems);
}
public void deleteAllItems(){
new DeleteAllItemsAsyncTask(myItemDao).execute();
}
public LiveData<List<MyItem>> getAllItems(){
return allItems;
}
//Async tasks to put the items to the Database
private static class InsertAllItemsAsyncTask extends AsyncTask<List<MyItem>,Void,Void>{
MyItemDao myItemDao;
private InsertAllItemsAsyncTask(MyItemDao myItemDao){
this.myItemDao = myItemDao;
}
@Override
protected Void doInBackground(List<MyItem>... lists) {
myItemDao.insertAll(lists[0]);
return null;
}
}
private static class DeleteAllItemsAsyncTask extends AsyncTask<Void,Void,Void>{
MyItemDao myItemDao;
private DeleteAllItemsAsyncTask(MyItemDao myItemDao){
this.myItemDao = myItemDao;
}
@Override
protected Void doInBackground(Void... voids) {
myItemDao.deleteAllItems();
return null;
}
}
private static class DeleteItemListAsyncTask extends AsyncTask<List<MyItem>,Void,Void>{
MyItemDao myItemDao;
private DeleteItemListAsyncTask(MyItemDao myItemDao){
this.myItemDao = myItemDao;
}
@Override
protected Void doInBackground(List<MyItem>... myItems) {
myItemDao.delete(myItems[0]);
return null;
}
}
}
ViewModel
:
public class MyItemViewModel extends AndroidViewModel {
//LiveData is require for the RoomDatabase
private LiveData<List<MyItem>> allItems;
private MyItemRepository myItemRepository;
public MyItemViewModel(Application application){
super(application);
this.myItemRepository = new MyItemRepository(application);
allItems = myItemRepository.getAllItems();
}
public LiveData<List<MyItem>> getAllItems(){
return allItems;
}
public void insertAll(List<MyItem> myItems){
myItemRepository.insertAll(myItems);
}
//deletes a List of items
public void delete(List<MyItem> myItems){
myItemRepository.deleteItems(myItems);
}
public void deleteAllItems(){
myItemRepository.deleteAllItems();
}
}
CardView
I called it smallcard.xml
:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginVertical="5dp"
android:backgroundTint="#ffffff"
android:id="@+id/card_View">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/card_Image"
android:layout_width="100px"
android:layout_height="100px"
android:layout_marginStart="10dp"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/card_Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="Name"
android:textColor="?attr/colorPrimary"
android:textSize="18sp"
android:textStyle="bold"/>
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
The test Fragment.xml
:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MyDefaultFragment"
android:background="#ffffff">
<EditText
android:id="@+id/filterEditText"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="40dp"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:layout_below="@id/filterEditText"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
/>
</RelativeLayout>
some build.gradle(Module)
implementations
:
dependencies{
implementation 'androidx.room:room-runtime:2.3.0'
annotationProcessor 'androidx.room:room-compiler:2.3.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.3.1'
implementation 'androidx.lifecycle:lifecycle-livedata:2.3.1'
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.3.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
...
}
You will find more information as comments in the code.
UPDATE
In addition I provide you the whole project on github. I added different intents after clicking the Cards. It will open the same Fragment
for the same Card even you are searching and the positions are changed.