Some users are reporting this error:
java.lang.NullPointerException
at android.widget.ArrayAdapter.getCount(ArrayAdapter.java:291)
at android.widget.AutoCompleteTextView$PopupDataSetObserver$1.run(AutoCompleteTextView.java:1670)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:3687)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
at dalvik.system.NativeStart.main(Native Method)
Here is my code snippet:
private List<City> autoCompleteCities = new ArrayList<City>();
private List<City> autoCompleteCitiesOld = new ArrayList<City>();
private ArrayAdapter<String> autoCompleteAdapter;
private AutoCompleteTextView cityView;
...
autoCompleteAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line);
autoCompleteAdapter.setNotifyOnChange(true);
cityView = (AutoCompleteTextView) findViewById(R.id.city);
cityView.setAdapter(autoCompleteAdapter);
cityView.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
LogUtil.i("!!!!!!!!!!!!! ontextchanged", s.toString());
autoCompleteAdapter.clear();
autoCompleteCitiesOld = autoCompleteCities;
if (s.toString().length() > 2) {
autoCompleteCities = search(s.toString());
for (City city : autoCompleteCities) {
autoCompleteAdapter.add(city.getDisplayName());
}
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
It doesnt happen for me, but I have enough error reports to know it happens for several users. Any idea what is wrong? Why does this happen to only a handful of users?
I found this post but putting the fetching of new autocomplete values in an AsyncTask meant that the results always were one character behind what the user had entered.
Where are you passing the ArrayList to the ArrayAdapter. The NullPointerException throws while counting the list lenght in ArrayAdapter. But the list is not passed to Adapter, so the list object in default Adapter contains null.
My hypothesis is that you are somewhere using static variables which are nulled after activity recreation.
See my answer here: Public static variables and Android activity life cycle management
create android v.4 emulator, go to developer's settings, disable background tasks, disable multiple activities and then try to use your application - chances are it will fail.
Where did u have added the array of String ,the reason of error may be the empty array list
as:
autoCompleteAdapter = new ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line);
here u hav'nt assign any string array
And enter your full code it will help lot to the people to understand the problem
Better do null check before using object like below and do it in search function also..
if (s.toString().length() > 2) {
autoCompleteCities = search(s.toString());
if(autoCompeleteCities!=null){
for (City city : autoCompleteCities) {
autoCompleteAdapter.add(city.getDisplayName());
}
}
I had exactly the same problem with the same issue. In my case I have thousands of streets I dynamically add and remove from the list.
In my case it was always reproducable when I enter some value into the AutoComplete field, let's say 3 characters (while threshold is set to 0), so the dropdown list appears, and then I delete those characters by delete button very quickly. That means I hit the delete button a lot of times. It always crashes then. I figured out that clear causes the crashes. I put the clear in beforeTextChanged and the crashes were gone:
public class StreetTextWatcher implements TextWatcher {
private final StreetArrayAdapter adapter;
private boolean alreadyAdded = false;
public StreetTextWatcher(StreetArrayAdapter adapter) {
this.adapter = adapter;
}
#Override
public void afterTextChanged(Editable s) {
//not used
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (s.length() < 1) {
adapter.clear();
alreadyAdded = false;
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() == 1) {
if (s.toString().toLowerCase(Locale.GERMAN).startsWith("a") && !alreadyAdded) {
adapter.addAll(StreetNames.STREETS_A);
alreadyAdded = true;
}
if (s.toString().toLowerCase(Locale.GERMAN).startsWith("b") && !alreadyAdded) {
adapter.addAll(StreetNames.STREETS_B);
alreadyAdded = true;
}
//more streets...
}
}
}
StreetArrayAdapter:
public class StreetArrayAdapter extends ArrayAdapter<String> {
public StreetArrayAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
}
public void addAll(String[] streets) {
for (String street : streets) {
add(street);
}
}
}
Related
I am working in an Android studio project. Here I want to implement one searching system. I have an edittext and an imagebutton. When user writers something in edittext and press imagebutton, system shows the relevant data from some database. This much I have covered.
Moreover, I want to implement a system where while user writes at least a specific length of letters (say 3) in edittext, the searching will start automatically. With more adding of letters the searching will be filtered accordingly. Is it possible to do this? Or something similar to this?
on searchview there is this syntax. just check the length of the text
public void onQueryTextChange(String query) {
if(query.length() >= 3) {
searchStarts();
}
}
or if you dont use search view just use textwatcher
textWatcher = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (timer != null) {
timer.cancel();
}
}
#Override
public void afterTextChanged(Editable s) {
if(s.length() > 3){
searchStarts();
}
};
you have to add text watcher to your edit text. I will show you the solution with using debounce to avoid updating list all time user type a text.
So lets start with adding textWatcher to your edittext field:
searchField.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//this one is for all letters, you can check the s length eg if(s.length() >= 3)
searchSubject.onNext(s.toString());
}
#Override
public void afterTextChanged(Editable s) {
}
});
This solution use rxjava if you do not want to do it just replace searchSubject with calling your search method.
So the fields:
private PublishSubject<String> searchSubject = PublishSubject.create();
private Subscription searchSubscription;
private String currentSearchPrefix = "";
And subscription (call this method onResume):
/**
* Subscribe to searchSubject to update list of items depends on given prefix.
* Debounce on changes 500 milliseconds
*/
private void subscribeSearch() {
searchSubscription = searchSubject
.debounce(500, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.subscribe(result -> {
currentSearchPrefix = result;
MainActivity.this.runOnUiThread(this::refreshList);
});
}
My method refreshList just update list by filter it using currentSearchprefix by one of field. Remember to unsubscribe searchSubscription onPause().
I have an AutoCompleteTextView where i want to search for locations, example:
You type "vig" and the AutoCompleteTextView list shows the 5 best results for that:
"4560 Vig", "Juan Pablo Perez..", "The Vig 4041...", "Vig"
Another example, you type "vigo": it says the right place: "Vigo, Pontevedra" and you can select it and put it on the AutoCompleteTextView.
For now, what i have is working almost good but i have one error:
The display list is only showing when you delete on character, if not doesn't show, and what it shows is the last string result, example:
you have typed "vigo", and nothing appears, you delete the "o" and the display list shows results for "vigo" instead of "vig", that is what is typed in the AutoCompleteTextView in the moment.
I perform the search for the locations in an AsyncTask:
private class SearchAddress extends AsyncTask<String, Void, String[]> {
#Override
protected String[] doInBackground(String... params) {
//adapter.clear();
String[] addressArray = getStreetList(query);
return addressArray;
}
#Override
protected void onPostExecute(String[] addressArray) {
if(addressArray == null)
Toast.makeText(NewRouteActivity.this, "No address obtained from server", Toast.LENGTH_SHORT).show();
else{
adapter.clear();
for(String address: addressArray){
adapter.add(address); <------HERE IS THE 2ND ERROR
Log.d("ASYNC", address);
}
}
}
#Override
protected void onPreExecute() {}
#Override
protected void onProgressUpdate(Void... values) {}
}
Here is my AutoCompleteTextView Code:
String[] array = {};
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, array);//simple_dropdown_item_1line
autoFrom.setAdapter(adapter);
autoTo.setAdapter(adapter);
asyncSearch = new SearchAddress();
autoFrom.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (((AutoCompleteTextView) autoFrom).isPerformingCompletion()) {return;}
if (s.length() < 2) {
return;
}else{
query = s.toString();
if (asyncSearch.getStatus().equals(AsyncTask.Status.FINISHED)){
asyncSearch = new SearchAddress();
asyncSearch.execute(query);
Log.d("ASYNC", "FINISH GOOD");
Log.d("ASYNC", query);
}else{
Log.d("ASYNC", "CANCEL");
asyncSearch.cancel(false);
asyncSearch = new SearchAddress();
asyncSearch.execute(query);
}
return;
}
}
});
I hope with this is enough, getStreetList() is working good, giving good results.
If you need something else just ask.
Thanks in advance!!!
I don't know if this is going to help you, but I've had problems with TextWatcher and AutoCompleteTextView. You should be using a filter on an adapter and not a TextWatcher.
A good implementation of google places + AutoCompleteTextView by google:
https://developers.google.com/places/training/autocomplete-android
I manage an AutoCompleteTextView that should give all the towns (ville) found in my DB, according the 4 first letters I put in.
The Async task works well and gets the right data.
My problem is that the DropDownList display NOT all the items. Often only 1, 2, 3 or 4 out of the 20 returned by the DB.
So I figured out, there should be some auto filtering within the ACTV itself!
I check many topics here on SO, to update my code but I didn't succeed.... :-(
I keep getting errors without knowing exactly what the trouble is! :-(
AutoCompleteTextView force to show all items
AutoCompleteTextView - disable filtering
So here is my code:
class MyActivity extends Activity implements AdapterView.OnItemClickListener
{
static class Ville
{
String id;
String name;
#Override
public String toString() { return this.name; }
};
ArrayAdapter<Ville> villeAdapter;
String villeAdapterFilter;
VilleUpdateTask villeAdapterUpdateTask;
AutoCompleteTextView villeText;
Ville selectedVille;
final TextWatcher textChecker = new TextWatcher() {
public void afterTextChanged(Editable s) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count)
{
MyActivity.this.setAdapterFilter(s.toString());
}
};
public void onCreate(Bundle bundle)
{
super.onCreate(bundle);
setContentView(R.layout.main);
this.villeAdapter = new ArrayAdapter<Ville>(this,android.R.layout.simple_dropdown_item_1line, new Ville[0]);
this.villeText = (AutoCompleteTextView ) findViewById(R.id.villeSelector);
this.villeText.setAdapter(this.villeAdapter);
this.villeText.setThreshold(THRESHOLD_DROPDOWN);
this.villeText.setOnItemClickListener(this);
this.villeText.addTextChangedListener(textChecker);
}
public void onDestroy() { stopVilleAdapterUpdate();
public void setAdapterFilter(String filter)
{
if (filter == null) {
// clearing the adapter
this.villeAdapterFilter = null;
this.villeAdapter.clear();
this.villeAdapter.notifyDataSetChanged();
Log.d("MyActivity","Clearing ville filter !");
} else if (filter.length() > THRESHOLD_QUERY) {
if (this.villeAdapterFilter == null) {
Log.d("MyActivity","Ville Adapter Filter defined to:"+filter);
this.villeAdapterFilter = filter;
startVilleAdapterUpdate();
} else {
Log.d("MyActivity","Already filtered with:"+this.villeAdapterFilter);
}
} else {
Log.d("MyActivity","Resetting filter (not enough data)");
this.villeAdapterFilter = null;
this.villeAdapter.clear();
this.villeAdapter.notifyDataSetChanged();
}
}
public synchronized void onItemClick(ViewAdapter<?> ad, View v, int position, long id)
{
this.selectedVille = this.villeAdapter.getItemAtPosition(position);
Log.d("MyActivity","Ville selected: "+this.selectedVille);
}
public synchronized void startVilleAdapterUpdate()
{
stopVilleAdapterUpdate();
Log.d("MyActivity","Starting Update of Villes with "+this.villeAdapterFilter);
this.villeAdapterUpdateTask = new VilleUpdateTask();
this.villeAdapterUpdateTask.execute(this.villeAdapterFilter);
}
public synchronized void stopVilleAdapterUpdate()
{
if (this.villeAdapterUpdateTask != null) {
Log.d("MyActivity","Stopping current update of villes");
this.villeAdapterUpdateTask.cancel(true);
this.villeAdapterUpdateTask = null;
}
}
public synchronized void onVilleAdapterUpdateResult(Ville[] data)
{
this.villeAdapterUpdateTask = null;
if (data != null) {
Log.d("MyActivity","Received "+data.length+" villes from update task");
this.villeAdapter.clear();
this.villeAdapter.addAll(data);
this.villeAdapter.notifyDataSetChanged(); // mise à jour du drop down...
}
}
class VilleUpdateTask extends AsyncTask<String,Void,Ville[]>
{
public Ville[] doInBackground(String ... filters)
{
ArrayList<Ville> values = new ArrayList<Ville>();
try {
HttpClient httpclient = new DefaultHttpClient();
....
....
for(int i=0;i<json_array.length();i++) {
JSONObject json_ligne = json_array.getJSONObject(i);
try {
Ville v = new Ville();
v.name = json_ligne.getString("NAME_VILLE");
v.id = json_ligne.getString("ID_VILLE");
values.add(v);
} catch (Exception ex) {
Log.w("VilleUpdateTask","Invalid value for Ville at index #"+i,ex);
}
}
} catch (Exception ex) {
Log.e("VilleUpdateTask","Failed to retrieve list of Ville !",ex);
}
return values.toArray(new Ville[values.size()]);
}
public void onPostExecute(Ville[] data)
{
MyActivity.this.onVilleAdapterUpdateResult(data);
}
}
}
EDIT 1: yep sorry, my ACTV is a basic TextView, it is not a scrolling problem because on better case I can see 10 items in the list, and last the position is random
EDIT 2: could you just help me to adapt my existing code to the given solutions from the 2 URLs above?
(1) according to that solution AutoCompleteTextView - disable filtering
I have to:
create my class ClassMyACArrayAdapter which is the same as the given one, only its name changes
change my declaration from
ArrayAdapter villeAdapter;
to
List<ClassMyACArrayAdapter> villeAdapter;
but in the onCreate what should replace the initial
this.villeAdapter = new ArrayAdapter
(this,android.R.layout.simple_dropdown_item_1line, new Ville[0]);
Just call autoCompleteTextView.showDropDown() Whenever you need it.....cheers :)
Is your AutoCompleteTextView a TextView, LinearLayout o ListView? The code in your activity looks fine, so I'm guessing that the problem could be in the layout (maybe you're not using a scroll so you can only see the first values).
Also, the values that you see are always the first ones in the returned list or they're at random positions?
Aim :
I want to get some strings from webservice and populate AutoCompleteTextView with them.This is simple but what I want actually is to begin search (invoke webservice) when typing finished.
I mean, for example, I type something and 3 seconds after typing finished, AutoCompleteTextView would get populated and suggestions show up.
What I did so far :
As you can see in the code below, I used a CountDownTimer to achieve this. I set it 3 seconds and start it in OnTextChanged. When user type, I clear CountDownTimer and create a new instance of it and start it again.
So, whenever user type -on every key press- I reset counter.
After that, I call my method - which invoke webservice and populate AutoCompleteTextView - in CountDownTimer's OnFinish().
Problem :
When I finish typing, everything works as expected as I can see in debug mode. But suggestions doesn't come up FOR ONLY FIRST SEARCH.
I mean, it works as well but AutoCompleteTextView doesn't get populated with data for only FIRST TIME.
Note :
I invoke webservice both synchronous and asynchronous way.
counter=new MyCount(3000, 1000);
autoComplete.addTextChangedListener(new TextWatcher(){
public void afterTextChanged(Editable editable) {
if(isBackPressed){ //catch from KeyListener, if backspace is pressed don't invoke web service
isBackPressed=false;
return;
}
String newText = editable.toString();
searchText=newText;
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(isBackPressed){
isBackPressed=false;
return;
}
counter.cancel();
counter=new MyCount(3000, 1000);
counter.start();
}
});
Here is my CountDownTimer Class
public class MyCount extends CountDownTimer{
public MyCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
#Override
public void onFinish() {
invoke_webservice(searchText);
}
#Override
public void onTick(long millisUntilFinished) {
}
}
Here is my method to invoke webservice in synchronous way
public void invoke_webservice(String key){
try{
. //code to invoke webservice and populate string [] results
.
.
aAdapter = new ArrayAdapter<String>(getApplicationContext(),R.layout.item,results);
autoComplete.setAdapter(aAdapter);
aAdapter.notifyDataSetChanged();
}
Here is my method to invoke webservice in asynchronous way
class getJson extends AsyncTask<String,String,String>{
#Override
protected String doInBackground(String... key) {
String newText = key[0];
. //code to invoke webservice and populate string [] results
.
.
runOnUiThread(new Runnable(){
public void run(){
aAdapter = new ArrayAdapter<String>(getApplicationContext(),R.layout.item,results);
autoComplete.setAdapter(aAdapter);
aAdapter.notifyDataSetChanged();
}
});
return null;
}
}
Thanks in advance
Here is what you can do:
i have set the threshold value to 3
atvSearchPatient.setThreshold(3);
apply the text watcher listener on autocomplete text view like below:
//here set the text watcher
atvSearchPatient.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
String str = atvSearchPatient.getText().toString().trim();
if (str.length() >2) {
searchPatient(str );
}
}
});
on your first call hit API with null values and for second and consecutive calls with string from autocomplete textview field as above.and then in response apply the adapter like below:
this.nameList.clear();
if (nameList.size() > 0) {
this.nameList.addAll(dataFromResponseList);
atvSearchPatient.setAdapter(null);
ArrayAdapter<String> adapter = new ArrayAdapter<>(
this, android.support.v7.appcompat.R.layout.select_dialog_item_material, nameList);
atvSearchPatient.setAdapter(adapter);
adapter.notifyDataSetChanged();
atvSearchPatient.showDropDown();
It's weired problem for first time not working but that's the only way working in my case nothing else. hope it helps
Thanks
well i'm trying to update the autoCompleteTextview ArrayAdapter dynamiclly each time text changed by using TextWathcer.
every time text changed there is an http request in a new AsyncTask and in the callback from the async task (onPostExecute) i call clear() from the adapter and adding new items to him and then call notifyDataSetChanged.
Unfortunately the auto complete drop down is never shown..
please, where do i go worng?!
here is the code:
AutoCompleteTextView acCountry = (AutoCompleteTextView)layout.findViewById(R.id.autoComplete);
final ArrayAdapter<RMAutoComplete.ListItem> countriesAdapter = new ArrayAdapter<RMAutoComplete.ListItem>(this.context,android.R.layout.simple_dropdown_item_1line);
acCountry.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() > 1)
{
new AsyncTask<String, Void, List<RMAutoComplete.ListItem>>(){
#Override
protected List<ListItem> doInBackground(String... params) {
List<ListItem> l = null;
try {
l = location.getCountryData(params[0]);
} catch (Exception e) {
Log.e(TAG,"error when getCountryData",e);
}
return l;
}
#Override
protected void onPostExecute(List<ListItem> countries) {
countriesAdapter.clear();
if (countries != null)
for (ListItem listItem : countries) {
countriesAdapter.add(listItem);
}
countriesAdapter.notifyDataSetChanged();
}
}.execute(s.toString());
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void afterTextChanged(Editable s) {}
}
);
Common mistake : Did you set the adapter to acCountry at all?