Android Activity freezes when search from database - java

I have a textWatcher for my editText. The idea is:
I have two list views and they are both updated after every inserted in editText field character. Somehow I could't implement this using standart ArrayAdapter with filter method. So I had to make my own realization of search. At the beggining it seemed good, but as soon as I added to my data base more less enough data the interface started to freeze.
This is the call
inputSearch.addTextChangedListener(createAndReturnTextWatcher());
And this is the method that creates and returns textWatcher
private TextWatcher createAndReturnTextWatcher() {
final List<String> list = dbExtractor.getDataForSearchAdapters();
TextWatcher watcher1;
watcher1 = new TextWatcher() {
#Override
public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {
if (cs.length() == 0) {
lv.setAdapter(null);
lv2.setAdapter(null);
dc.clickedOnce = true;
dc.decrement();
dc.checkDimCounter();
} else {
setSecondListData(list, String.valueOf(cs));
setFirstListData(list, String.valueOf(cs));
if (dc.clickedOnce) {
dc.increment();
dc.checkDimCounter();
dc.clickedOnce = false;
}
}
}
#Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
int arg3) {
}
#Override
public void afterTextChanged(Editable arg0) {
}
};
return watcher1;
}
And this are methods that perform filtering
private void setSecondListData(List<String> inputData, String cs) {
List<String> result = turkishSearcher.performFiltering(inputData, cs);
ArrayAdapter turkAdapt = new ArrayAdapter(this, R.layout.dictionary_list_item, R.id.product_name,
result);
lv2.setAdapter(turkAdapt);
}
private void setFirstListData(List<String> inputData, String cs) {
List<String> result = normSearcher.performFiltering(inputData, cs);
ArrayAdapter turkAdapt = new ArrayAdapter(this, R.layout.dictionary_list_item, R.id.product_name,
result);
lv.setAdapter(turkAdapt);
}
And this is the class that filters data
public class TurkishSearcher extends Searcher{
#Override
int returnPosition() {
return 1;
}
#Override
String reverse(String couples) {
java.lang.String[] arr = couples.split(" — ");
return arr[1]+ " — " + arr[0];
}
#Override
String replaceTurkish(String words) {
if (checkWithRegExp(words)) {
return words.toLowerCase().replaceAll("ç", "c").replaceAll("ğ", "g").replaceAll("ı", "i").
replaceAll("ö", "o").replaceAll("ş", "s").replaceAll("ü", "u");
} else return words;
}
public static boolean checkWithRegExp(String word){
Pattern p = Pattern.compile("[öçğışü]", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(word);
return m.find();
}
}
I clearly understand that the way I keep data is wrong and the search algorhytm isn't good as well. But my question is:
Is it possible to avoid freezes using Threads for search implementations. Can i puth both searchers to their own threads to somehow avoid interface freezes? If it's possible, pls tell me the best place in code where I can use threads.
Thank you in advance!

You should never block the main thread by database operations. Instead use:
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... unusedParams) {
// TODO: do your database stuff
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
// TODO: refresh UI elements, because thread finished
super.onPostExecute(aVoid);
}
}.execute();

Best way to do long tasks in background thread. You can use asynctask to do this.

Related

Passing a variable between functions

I'm new to android studio and Java. My app uses jsoup to pass the contents of a website into an array where each element gets displayed on swipable flashcards (like tinder)
I've got a problem where my app crashes when I try to pass the result of the variable 'words' from onPostExecute() (line 123) to String num on line 49. I want to take the output of the function in onPostExcecute and set it as String num but I'm not sure how to do it.
public class AppHome extends AppCompatActivity implements PopupMenu.OnMenuItemClickListener {
TextView texx;
private ArrayList<String> al;
private ArrayAdapter<String> arrayAdapter;
private int i;
String words;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app_home);
texx= findViewById(R.id.text1);
new doit().execute();
String num = words;
String str[] = num.split(",");
final ArrayList al = new ArrayList<String>(Arrays.asList(str));
arrayAdapter = new ArrayAdapter<>(this, R.layout.item, R.id.helloText, al );
SwipeFlingAdapterView flingContainer = (SwipeFlingAdapterView) findViewById(R.id.frame);
registerForContextMenu(flingContainer);
flingContainer.setAdapter(arrayAdapter);
flingContainer.setFlingListener(new SwipeFlingAdapterView.onFlingListener() {
#Override
public void removeFirstObjectInAdapter() {
// this is the simplest way to delete an object from the Adapter (/AdapterView)
Log.d("LIST", "removed object!");
al.remove(0);
arrayAdapter.notifyDataSetChanged();
}
#Override
public void onLeftCardExit(Object dataObject) {
//Do something on the left!
//You also have access to the original object.
//If you want to use it just cast it (String) dataObject
Toast.makeText(AppHome.this, "left", Toast.LENGTH_SHORT).show();
}
#Override
public void onRightCardExit(Object dataObject) {
Toast.makeText(AppHome.this, "right", Toast.LENGTH_SHORT).show();
}
#Override
public void onAdapterAboutToEmpty(int itemsInAdapter) {
// Ask for more data here
al.add("XML ".concat(String.valueOf(i)));
arrayAdapter.notifyDataSetChanged();
Log.d("LIST", "notified");
i++;
}
#Override
public void onScroll(float scrollProgressPercent) {
}
});
}
public class doit extends AsyncTask<Void,Void,Void> {
//String words;
#Override
protected Void doInBackground(Void... voids) {
try {
Document doc = Jsoup.connect("https://screenscrape4top40.000webhostapp.com/").get();
words=doc.text();
}catch(Exception e){e.printStackTrace();}
return null;
}
#Override
public void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
texx.setText(words);
//String str = (words);
//List<String> elephantList = Arrays.asList(str.split(","));
//texx.setText(elephantList.toString());
// texx.setText(elephantList);
}
}
}
public class doit extends AsyncTask<Void, Void, String> {
#Override
protected Void doInBackground(Void... voids) {
String words = "";
try {
Document doc = Jsoup.connect("https://screenscrape4top40.000webhostapp.com/").get();
words = doc.text();
} catch(Exception e) {
e.printStackTrace();
}
return words;
}
#Override
public void onPostExecute(String words) {
super.onPostExecute(aVoid);
texx.setText(words);
//String str = (words);
//List<String> elephantList = Arrays.asList(str.split(","));
//texx.setText(elephantList.toString());
// texx.setText(elephantList);
}
}
It should be fine now.
The problem is, you are not returning anything from the doInBackground method and hence you are not getting anything in the onPostExecute function.
You might consider checking the documentation for AsyncTask here.
In doInBackgroud() return the string(make sure it never beacome null) you want to use it on PostExecute()
also changed the data type of Parameter in onPostExecute method
onPostExecute(String Strs)
Then pass it to supper.OnPostExecute().
then u can use it.

Is there a more efficient way to update the listview or loop through a string array

I am currently trying to write a simple word search program for android. It is supposed to match each letter searched by the user and display all matches from a local dictionary.
Everything is working for a smaller dictionary but when I try to use a larger one the program collapses. My guess is that my search function that loops through each word is to inefficient.
Following is the code for my project.
public class MainActivity extends ActionBarActivity {
String[] items;
int length;
ArrayList<String> listItems;
ArrayAdapter<String> adapter;
ListView listView;
EditText editText;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView=(ListView)findViewById(R.id.listview);
editText=(EditText)findViewById(R.id.txtsearch);
items = readFile().split("\\n");
initList();
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
length=s.toString().length();
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().equals("")) {
initList();
}
else {
searchItem(s.toString());
}
}
#Override
public void afterTextChanged(Editable s) {
if (s.toString().length() < length) {
initList();
for (String item:items) {
if (!item.toLowerCase().contains(s.toString().toLowerCase())) {
listItems.remove(item);
}
}
}
}
});
}
public String readFile(){
InputStream input = getResources().openRawResource(getResources().getIdentifier("words","raw", getPackageName()));
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String finalstring = "";
try {
String line;
br = new BufferedReader(new InputStreamReader(input));
while ((line = br.readLine()) != null) {
// finalstring += line + "\n";
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
public void searchItem (String searchword) {
for(String item:items){
if(!item.contains(searchword)){
listItems.remove(item);
}
}
adapter.notifyDataSetChanged();
}
public void initList(){
listItems=new ArrayList<>(Arrays.asList(items));
adapter=new ArrayAdapter<String>(this, R.layout.list_item, R.id.txtitem, listItems);
listView.setAdapter(adapter);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
As previously mentioned, I believe that my SearchItem(String SearchWord) is the problem but maybe there is something wrong with the data update of the adapter?
Note: I have used breakpoints and it crashes when the loop within SearchItem(String SearchWord) is called upon.
Many thanks guys!
I changed the editText and used searchView instead. That fixed the problem!
Put your code in handler and it will run after sometime of your search.
new Handler().postDelayed(new Runnable(
public void run(){
// your update code here..
}
), 3000);
This is called breathing time while user is typing in the edit text. so as soon as he start typing it will not start searching the word but after 3 second it will start searching.
Hope this will help you.
Okay there are many issues here I like to address:
If you have a large data set and want to Search in it, I would highly recommend using an SQLite Database. That is exactly what this databases are made for. See Storing and Searching for Data for further details
Long running tasks like this should always run as an AsyncTask
Consider your storage management! Eg. your readFile reads a file to an String, and than splits it to an Array. Why not directly split it while loading. You keep an array of all strings then afterwards create a list from it and afterwards copy it into an ArrayList. From this list you remove all entries that you do not need. When you think about all the copy work and how often you code runs threw the list, I think you can find a much faster solution by eliminating tasks!

AutoCompleteTextView with Parse.com (Android)

I'm new to Android in general, so I looked up the documentation for AutoCompleteTextView.
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line, USERS);
AutoCompleteTextView textView = (AutoCompleteTextView)
findViewById(R.id.searchUserTextField);
textView.setAdapter(adapter);
I see that it's up and running with very little code.
private static final String[] COUNTRIES = new String[] {
"user1", "user1", "user3", "user4", "user5"
};
I realize this is a stretch, but what would the next steps be with respect to implementing an autocomplete function based on my ParseUser table, especially as the text in the AutoCompleteTextView is changed by one character at a time?. Obviously, I wouldn't populate the USERS array with a Parse query displaying all of my users on each attempted search. How would I logically arrange such a thing?
To begin with, I'd probably include a TextChangedListener:
adapter.setNotifyOnChange(true);
textView.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before,
int count) {
if (count % 3 == 1) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
textview.setDropDownHeight(LayoutParams.WRAP_CONTENT);
adapter.clear();
// Run my background task here
}
}, 1000);
}
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
public void afterTextChanged(Editable s) {
}
});
textView.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterview, View v,
int position, long arg3) {
// TODO Auto-generated method stub
searchUserTextField.setText(adapterview.getItemAtPosition(position)
.toString());
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
});
But what would I provide as the background task? What am I querying so that I'm not searching through, say, 1000 users all at once, which is how many users I have in my app currently?
This seems to work. It's static in the sense that it always prints out the first 100 users in your User class. I'm sure you could add a constraint that matches the first or second letters of the AutoCompleteTextView to the usernames.
ParseQuery<ParseUser> userQuery = ParseUser.getQuery();
userQuery.findInBackground(new FindCallback<ParseUser>() {
public void done(List<ParseUser> parseUsers, ParseException e) {
if (e == null) {
Log.d("users", "Retrieved " + parseUsers.size());
ParseUser[] data = parseUsers.toArray(new ParseUser[parseUsers.size()]);
String[] strings = new String[data.length];
for (int i = 0; i < data.length; i++) {
strings[i] = data[i].getString(ParseConstants.KEY_USERNAME);
}
// Test to see if it was correctly printing out the array I wanted.
// System.out.println(Arrays.toString(strings));
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_dropdown_item_1line, strings);
AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.searchUserTextField);
if(parseUsers.size() < 40) textView.setThreshold(1);
else textView.setThreshold(2);
textView.setAdapter(adapter);
} else {
Log.d("users", "Error: " + e.getMessage());
}
}
});

Get position of element in listview when TextWatcher triggers

I'm having problems to get the position of my View inside a ListView when TextWatcher triggers for changes in EditText.
Each CardView has two EditTexts and two Spinners. When I make some change in the values for the name of the product (the EditText in the left) and for the spinners, my code get the correctly the position of the CardView in the list.
However, when I change the value of the price by typing it, my code cannot get it's position.
The position of the CardView is gotten in the line...
final int posicao = Integer.parseInt(consumableInfo.getName()), which consumableInfo is the class listed in my Adapater, and consumableInfo.getName gets the name of the card, which is equal to the position of the card. Like "0", "1", "2"...
This happens because everytime I call...
holder.mAutoCompleteTextView.setOnItemClickListener for the AutoCompleteEditText on the left;
holder.mDivideConsumableSpinner.setOnItemSelectedListener for each spinner;
...my code iterates again over BindData. However, when I call...
holder.mConsumablePriceTextView.addTextChangedListener(priceTextWatcher) for the EditText on the right;
... my code DO NOT iterates again.
I'm trying to find another way to get it's position, but I'm having problems with that. Maybe forcing a way to posicao get the value, or creating a customTextWatcher that implements TextWatcher and gets consumableInfo as a parameter.
public class ConsumableAdapter extends RecyclerView.Adapter<ConsumableAdapter.ConsumableViewHolder> {
/*...some code ommited...*/
int posicaoGlobal;
public ConsumableAdapter(Context context, List<ConsumableInfo> contactList) {...}/*...some code ommited...*/
}
public class ConsumableViewHolder extends RecyclerView.ViewHolder {
public AutoCompleteTextView mAutoCompleteTextView;
public Spinner mDivideConsumableSpinner;
public Spinner mUnitsConsumableSpinner;
public EditText mConsumablePriceTextView;
public ConsumableViewHolder(View itemView) {
/*...*/
}
public void bindData(ConsumableInfo consumableInfo, ConsumableViewHolder holder, Context context) {
final int posicao = Integer.parseInt(consumableInfo.getName());
posicaoGlobal = posicao;
ArrayAdapter adapter = new ArrayAdapter(mContext, android.R.layout.select_dialog_item,
Constants.CONSUMABLE_CONSTANTS);
holder.mAutoCompleteTextView.setAdapter(adapter);
/* position is updated withmAutoCompleteTextView.setOnItemClickListener */
holder.mAutoCompleteTextView.setOnItemClickListener(new AdapterView.OnItemClickListener({
updateTotalPrice(posicao);
/*...*/
});
/*position is NOT updated with addTextChangedListener*/
holder.mConsumablePriceTextView.addTextChangedListener(priceTextWatcher);
/*position is updated with setOnItemSelectedListener in both Spinners*/
holder.mDivideConsumableSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
updateTotalPrice(posicao);
/*...*/
});
//product units
holder.mUnitsConsumableSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
updateTotalPrice(posicao);
/*...*/
});
}
private void updateTotalPrice(int posicao) {
/*...*/
mTotalPrice = getTotalPrice(BotequimActivity.mProductList, mPercent);
BotequimActivity.mTotalPriceTextView.setText(getTotalPriceString());
FormatStringAndText.setPriceTextViewSize(mTotalPrice, BotequimActivity.mTotalPriceTextView);
}
}
private void updateTotalPrice(int posicao, String priceString) {
/*...*/
BotequimActivity.mTotalPriceTextView.setText(getTotalPriceString());
FormatStringAndText.setPriceTextViewSize(mTotalPrice, BotequimActivity.mTotalPriceTextView);
}
private final TextWatcher priceTextWatcher = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (count != 0) {
if (FormatStringAndText.isNumeric(s.toString())) {
mProductPriceBeforeChange = Double.parseDouble(s.toString());
}
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Toast.makeText(mContext, "posicao =" + posicaoGlobal, Toast.LENGTH_SHORT).show();
if (s.toString().length() == 0) {
updateTotalPrice(posicaoGlobal, "0.00");
} else {
if (!isAutoCompleteClicked) {
if (FormatStringAndText.isNumeric(s.toString())) {
mProductPriceAfterChange = Double.parseDouble(s.toString());BotequimActivity.mTotalPriceTextView.setText(getTotalPriceString());
// FormatStringAndText.setPriceTextViewSize(mTotalPrice, BotequimActivity.mTotalPriceTextView);
updateTotalPrice(posicaoGlobal, s.toString());
} else {
}
} else {
isAutoCompleteClicked = false;
}
}
}
#Override
public void afterTextChanged(Editable s) {
}
};
public Double getTotalPrice(ArrayList<Product> productList, Double percent) {
mTotalPrice = 0;
for (Product product : productList) {
mTotalPrice = mTotalPrice + percent * (product.getUnits() * (product.getDoublePrice()) / product.getDividedBy());
}
return mTotalPrice;
}
}
You need to save the position when you create the TextWatcher. I would do this with an inner subclass:
// this is an inner class so it will have an implicit reference to
// the adapter (ConsumableAdapter.this)
public class PriceTextWatcher implements TextWatcher {
private int mPos;
public PriceTextWatcher(int position) {
super();
mPos = position;
}
// now add your TextWatcher implementation here and use mPos for position
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// ...
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// ...
}
#Override
public void afterTextChanged(Editable s) {}
}
Now you can initialize the position when you create the TextWatcher:
holder.mConsumablePriceTextView.addTextChangedListener(new PriceTextWatcher(posicao));
You will have multiple TextWatchers instead of the single final TextWatcher you currently have, but that's the trade-off for getting the position value where it needs to be.
Solved. I had to call priceTextWatcher as an argument with it's constructor, just like AdapterView.OnItemSelectedListener(). The correct one is:
holder.mConsumablePriceTextView.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//...
}
//...methods inside here
}

How to search in ArrayList of Custom Objects and then Update ListView with results?

I want to make a search from an ArrayList of custom objects and from those objects populate a ListView but I really don't know where to start....
Here's what I've got.
I get the search from a EditText and in an EditorActionListener I know when to start looking:
edittext.setOnEditorActionListener(new OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) {
//I got this from this site, another question
int searchListLength = medicos.size();
for (int i = 0; i < searchListLength; i++) {
if (medicos.get(i).getNombre().contains(edittext.getText().toString()))) {
//This is where Im suposed to do something but I dont know what to use to fill the ListView
}
}
return false;
}
});
Following is the structure of the custom object:
public class Medico implements Serializable{
private static final long serialVersionUID = 1L;
String nombre, especialidad1,especialidad2,especialidad3,celular,mail,telefono1,telefono2,ext,hospital,direccion;
double latitud,longitud;
public Medico(){}
public Medico(String nombre, String esp1,String esp2, String esp3, String cel,String mail,String tel1,String tel2, String ext,String hospital, String direccion, double lat, double lon){
this.nombre=nombre; //and so on.....
}
public void setNombre(String s){
this.nombre=s;
}
public void setEspecialidad1(String s){
this.especialidad1=s;
}
public String getNombre(){
return this.nombre;
}
public String getEspecialidad1(){
return this.especialidad1;
}
}
and so on.....
Thanks!
UPDATE (Code to update Adapter):
public void updatedData(ArrayList<Medico> itemsArrayList) {
myAdapter.clear();
if (itemsArrayList != null){
for (Medico object : itemsArrayList) {
//myAdapter.insert((Medico) object, myAdapter.getCount());
myAdapter.addAll(object);
//myAdapter.addAll(itemsArrayList);
}
}
myAdapter.notifyDataSetChanged();
}
You need to update the Adapter that you are using to to populate the ListView.
edittext.setOnEditorActionListener(new OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) {
//I will store the matches on this list.
List<Medico> resultado = new ArrayList<Medico>();
int searchListLength = medicos.size();
for (int i = 0; i < searchListLength; i++) {
if (medicos.get(i).getNombre().contains(searchsds)) {
// I found a match, I will add it to results
resultado.add(medicos.get(i));
}
}
//At the end, update the Adapter. I will assume that you have something like this.
myAdapter.setValues(resultado);
//You must write the code to set the values on your adapter. If you could post the way you are populating the list view I could help you out with more information.
return false;
}
});

Categories