I am writing a small google talk client for android and I am having trouble refreshing my ListView correcty.
This list contains the contact list and is showing the name and the presence of the contact. My listener works fine and I can see the presence changes of each contact in the log cat window, but my ListView is not refreshing... here is some code:
package de.marc.messenger;
// ofc here are the imports
public class RosterActivity extends Activity {
private Roster _roster;
private XMPPConnection _connection;
private List<HashMap<String, String>> _buddies;
private BuddyAdapter _adapter;
private ListView _list;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.roster);
_buddies = new ArrayList<HashMap<String, String>>();
_connection = LoginActivity.CONNECTION;
makePauseForRoster();
_roster = _connection.getRoster();
addRosterListener();
fillBuddyList();
sortBuddyList();
initializeListView();
}
/**
* Lets the thread sleep for a second to ensure that the presence of every
* user will be available
*/
private void makePauseForRoster() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Adds a listener to the roster, primarily for changes of presence
*/
private void addRosterListener() {
_roster.addRosterListener(new RosterListener() {
public void presenceChanged(Presence presence) {
String user = presence.getFrom().split("/")[0];
HashMap<String, String> buddy = findBuddyInRoster(user);
String p = getPresenceString(user);
buddy.put("presence", p);
System.out.println(buddy.values().toString()); // this works
_adapter.notifyDataSetChanged(); // this doesn't
_list.invalidate(); // this neither
}
});
}
/**
* Fills the list view with the roster entries
*/
private void initializeListView() {
_adapter = new BuddyAdapter(this, R.layout.roster_item,
_buddies);
_list = (ListView) findViewById(R.id.list_roster);
_list.setAdapter(_adapter);
}
/**
* Fills the buddy list with relevant data from a RosterEntry. Relevant data
* is the users' name, email and presence
*/
private void fillBuddyList() {
// this just fills my list of hashmaps (_buddies)
}
/**
* Get a predefined String depending on the presence of a user
*/
private String getPresenceString(String user) {
// something like "available: away ()" -> "away"
}
/**
* Sorts the buddy list. Only criterion is the presence of the user, because
* we have linear algorithms for this kind of problem.
*/
private void sortBuddyList() {
// move all offline contacts to the end
// move all online contacts to the beginning
// all other kind of contacts will stay in the middle
}
/**
* Finds a specific buddy object for a user via his hashed email
*/
private HashMap<String, String> findBuddyInRoster(String user) {
for (HashMap<String, String> buddy : _buddies) {
if (user.equals(buddy.get("user"))) {
return buddy;
}
}
return null;
}
}
This works fine, everything is shown correctly.. only trouble seems to be in the addRosterListener() method, where the onPresenceChanged() is implemented..
Here is my adapter:
package de.marc.messenger;
// as well some imports
public class BuddyAdapter extends ArrayAdapter<HashMap<String, String>> {
private Context _context;
private List<HashMap<String, String>> _map;
private LayoutInflater _inflater;
public BuddyAdapter(Context context, int id, List<HashMap<String, String>> map) {
super(context, id, map);
_context = context;
_map = map;
_inflater = (LayoutInflater) _context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// find view of a single row in a listview
View row = convertView;
if (convertView == null) {
row = _inflater.inflate(R.layout.roster_item, null);
}
// get data for a specific row
String name = _map.get(position).get("name");
String user = _map.get(position).get("user");
String presence = _map.get(position).get("presence");
// extract views from the row view
TextView nameText = (TextView) row.findViewById(R.id.text_name);
TextView userText = (TextView) row.findViewById(R.id.text_id);
ImageView presenceImg = (ImageView) row
.findViewById(R.id.image_presence);
// set data in extracted views
nameText.setText(name);
userText.setText(user);
int resource = 0;
// something is done with this variable
presenceImg.setImageResource(resource);
return row;
}
}
Is there anything I am missing?
Edit: I changed my onPresenceChanged method like this:
public void presenceChanged(Presence presence) {
String user = presence.getFrom().split("/")[0];
HashMap<String, String> buddy = findBuddyInRoster(user);
_adapter.remove(buddy);
String p = getPresenceString(user);
buddy.put("presence", p);
_adapter.add(buddy);
_adapter.notifyDataSetChanged();
}
It works to some extend: After swiping a bit on the screen, the contact that changed his presence is now out of the list :/
When an ArrayAdapter is constructed, it holds the reference for the List that was passed in. If you were to pass in a List that was a member of an Activity, and change that Activity member later, the ArrayAdapter is still holding a reference to the original List . The Adapter does not know you changed the List int he Activity.
It looks to me like that's what you're doing, so you need to recreate the adapter with the new list data.
Or you can use the ArrayAdapter methods to modify the underlying List (add, insert, remove, clear, etc.) Then notifyDataSetChanged wil work.
Related
Main Goal:-
I have a list of sports news. Each item contains a sport name and some info. Clicking on it will show the latest news regarding that particular sport. The user has the option to swipe to dismiss a news, if they don't want it in the list or they can also drag and drop it, for example, if they want to see some news on top of others.
Each item in the list is represented programmatically as a Sport.java object.
I want to retain the state of the list upon device orientation changes.
What I've tried:-
For the list, I have an arraylist of sport objects (ArrayList). I learned that to save a list of custom objects, they objects themselves need to be Parcelable. For this, I implemented the Parcelable.java interface like this:
package com.example.android.materialme;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
/**
* Data model for each row of the RecyclerView.
*/
class Sport implements Parcelable {
//Member variables representing the title and information about the sport
private String title;
private String info;
private String detail;
private final int imageResource;
/**
* Constructor for the Sport data model
* #param title The name if the sport.
* #param info Information about the sport.
*/
Sport(String title, String info, String detail, int imageResource) {
this.title = title;
this.info = info;
this.detail = detail;
this.imageResource = imageResource;
}
protected Sport(#NonNull Parcel in) {
title = in.readString();
info = in.readString();
detail = in.readString();
imageResource = in.readInt();
}
public static final Creator<Sport> CREATOR = new Creator<Sport>() {
#Override
public Sport createFromParcel(Parcel in) {
return new Sport(in);
}
#Override
public Sport[] newArray(int size) {
return new Sport[size];
}
};
String getTitle() {
return title;
}
String getInfo() {
return info;
}
int getImageResource(){
return imageResource;
}
String getDetail(){
return detail;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(title);
parcel.writeString(info);
parcel.writeString(detail);
parcel.writeInt(imageResource);
}
}
and then I used
outState.putParcelableArrayList(KEY, sportsList);
but, this doesn't work. The screen is just blank upon rotating device.
I tried debugging the app and found that the arraylist was being passed correctly with the data intact, it's just that the app is not being able to display it for some reason.
Also, the implementation of the fab button is so that it resets the whole list to its initial condition upon click. The fab works normally but if the orientation is changed once, it stops working (app doesn't crash). Changing the orientation back also doesn't fix the fab. So, to get the list again for any other test, I have to rerun the entire app.
Complete Code:-
MainActivity.java
package com.example.android.materialme;
import android.content.res.TypedArray;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
import java.util.Collections;
public class MainActivity extends AppCompatActivity {
//Member variables
private RecyclerView mRecyclerView;
private ArrayList<Sport> mSportsData;
private SportsAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(view -> resetSports());
//Initialize the RecyclerView
mRecyclerView = (RecyclerView)findViewById(R.id.recyclerView);
//Set the Layout Manager
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//Initialize the ArrayLIst that will contain the data
mSportsData = new ArrayList<>();
//Initialize the adapter and set it ot the RecyclerView
mAdapter = new SportsAdapter(this, mSportsData);
mRecyclerView.setAdapter(mAdapter);
initializeData(savedInstanceState);
ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
Collections.swap(mSportsData, from, to);
mAdapter.notifyItemMoved(from, to);
return true;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
mSportsData.remove(viewHolder.getAdapterPosition());
mAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
}
});
helper.attachToRecyclerView(mRecyclerView);
}
#Override
protected void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("state", mSportsData);
}
/**
* Method for initializing the sports data from resources.
*/
private void initializeData(Bundle savedInstanceState) {
if(savedInstanceState!=null){
mSportsData.clear();
mSportsData = savedInstanceState.getParcelableArrayList("state");
} else {
//Get the resources from the XML file
String[] sportsList = getResources().getStringArray(R.array.sports_titles);
String[] sportsInfo = getResources().getStringArray(R.array.sports_info);
String[] sportsDetail = getResources().getStringArray(R.array.sports_detail);
TypedArray sportsImageResource = getResources().obtainTypedArray(R.array.sports_images);
//Clear the existing data (to avoid duplication)
mSportsData.clear();
//Create the ArrayList of Sports objects with the titles and information about each sport
for (int i = 0; i < sportsList.length; i++) {
mSportsData.add(new Sport(sportsList[i], sportsInfo[i], sportsDetail[i], sportsImageResource.getResourceId(i, 0)));
}
sportsImageResource.recycle();
}
//Notify the adapter of the change
mAdapter.notifyDataSetChanged();
}
public void resetSports(){
initializeData(null);
}
}
App Images:-
#1 Initial List
#2 Changed List (Card #2 for sport basketball is swiped)
Orientation change to landscape:-
Even though the question is 4 months old and you probably don't need the answer anymore:
The problem is that you initialize the adapter with mSportsData, but reassign another value to the variable later in initializeData(). The ArrayList bound to the adapter is still the empty one it got initialized with.
A way to solve it would be to initialize mSportsData with either a new ArrayList if savedInstanceState is null or else the saved value, and to call initializeData only if savedInstanceState is null. You can remove the argument and therefore the if from initalizeData() completely.
// Initialize the ArrayList that will contain the data.
if (savedInstanceState == null) {
mSportsData = new ArrayList<>();
} else {
mSportsData = savedInstanceState.getParcelableArrayList("state");
}
//Initialize the adapter and set it ot the RecyclerView (nothing changed here)
mAdapter = new SportsAdapter(this, mSportsData);
mRecyclerView.setAdapter(mAdapter);
if (savedInstanceState == null) {
initializeData();
}
Remove this function call from OnCreate initializeData(savedInstanceState); and call it in OnResume
I couldn't get it working with the Parcelable way so what I tried doing (and it worked) was save all the info contained inside all the sport objects in separate lists and put those in the bundle instead.
In the initialiseData(), I just used these lists then to create a new list of sport objects with the exact data.
This shouldn't be the way of doing it but my app just doesn't loads the saved list if it's passed as a parcelable and I don't know why.
Here's the working code for now:-
#Override
protected void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
ArrayList<String> titles = new ArrayList<>();
ArrayList<String> infos = new ArrayList<>();
ArrayList<String> details = new ArrayList<>();
ArrayList<Integer> imgs = new ArrayList<>();
for(Sport sport: mSportsData){
titles.add(sport.getTitle());
infos.add(sport.getInfo());
details.add(sport.getDetail());
imgs.add(sport.getImageResource());
}
outState.putStringArrayList("title", titles);
outState.putStringArrayList("info", infos);
outState.putStringArrayList("detail", details);
outState.putIntegerArrayList("img", imgs);
}
private void initializeData(Bundle savedInstanceState) {
boolean hasData = false;
if(savedInstanceState!=null){
if(savedInstanceState.containsKey("title")){
//Get the resources from the bundle
ArrayList<String> sportsList = savedInstanceState.getStringArrayList("title");
ArrayList<String> sportsInfo = savedInstanceState.getStringArrayList("info");
ArrayList<String> sportsDetail = savedInstanceState.getStringArrayList("detail");
ArrayList<Integer> sportsImageResource = savedInstanceState.getIntegerArrayList("img");
//Clear the existing data (to avoid duplication)
mSportsData.clear();
//Create the ArrayList of Sports objects with the titles and information about each sport
for (int i = 0; i < sportsList.size(); i++) {
mSportsData.add(new Sport(sportsList.get(i), sportsInfo.get(i), sportsDetail.get(i), sportsImageResource.get(i)));
}
hasData = true;
}
}
if(!hasData) {
//Get the resources from the XML file
String[] sportsList = getResources().getStringArray(R.array.sports_titles);
String[] sportsInfo = getResources().getStringArray(R.array.sports_info);
String[] sportsDetail = getResources().getStringArray(R.array.sports_detail);
TypedArray sportsImageResource = getResources().obtainTypedArray(R.array.sports_images);
//Clear the existing data (to avoid duplication)
mSportsData.clear();
//Create the ArrayList of Sports objects with the titles and information about each sport
for (int i = 0; i < sportsList.length; i++) {
mSportsData.add(new Sport(sportsList[i], sportsInfo[i], sportsDetail[i], sportsImageResource.getResourceId(i, 0)));
}
sportsImageResource.recycle();
}
//Notify the adapter of the change
mAdapter.notifyDataSetChanged();
}
I tried to add new message item who arrived from push notification to list.
I tried to achieve this by live data. I used databinding in recyclerview and in main activity.
The func onChanged is not called when item is added to live data list in MsgViewModel class.
what I doing wrong?
public class MyFirebaseMessagingService extends FirebaseMessagingService {
......
private void showNotification(Map<String, String> data) {
id = data.get("id");
phone = data.get("phone");
locations = data.get("locations");
textMessage = data.get("textMessage");
MsgViewModel viewModel = new MsgViewModel(getApplication());
viewModel.addMessage(new Message(id, phone, locations, textMessage));
}
public class MsgViewModel extends AndroidViewModel {
private MutableLiveData<ArrayList<Message>> messageArrayList;
public MsgViewModel(#NonNull Application application) {
super(application);
messageArrayList = new MutableLiveData<>();
}
public void addMessage(Message message){
List<Message> messages = messageArrayList.getValue();
ArrayList<Message> cloneMessageList;
if(messages == null){
cloneMessageList = new ArrayList<>();
}else {
cloneMessageList = new ArrayList<>(messages.size());
for (int i = 0; i < messages.size(); i++){
cloneMessageList.add(new Message(messages.get(i)));
}
}
cloneMessageList.add(message);
messageArrayList.postValue(cloneMessageList);
}
public MutableLiveData<ArrayList<Message>> getMessageList(){
return messageArrayList;
}
}
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private MsgViewModel msgViewModel;
private MsgListAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.contentMainId.recyclerview.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
binding.contentMainId.recyclerview.setLayoutManager(layoutManager);
msgViewModel = new ViewModelProvider(this).get(MsgViewModel.class);
msgViewModel.getMessageList().observe(this, new Observer<ArrayList<Message>>() {
#Override
public void onChanged(ArrayList<Message> list) {
mAdapter = new MsgListAdapter(getApplication(), list);
binding.contentMainId.recyclerview.setAdapter(mAdapter);
// mAdapter.notifyDataSetChanged();
}
});
}
Any help why it is not update the adapter will be appreciated
==========Update=======
onChanged() method not called when have new item who added to the list
The problem here is how you instantiate your viewModels. If I understand correctly, you want them to be the same instance in both the activity and the messaging service.
One is MsgViewModel viewModel = new MsgViewModel(getApplication());
The other one is msgViewModel = new ViewModelProvider(this).get(MsgViewModel.class);
On the second one this stands for the current instance of the activity. And its context is different from the one you get from getApplication().
As far as I know, when you call 'postValue()' or something 'setValue()' method, you should give new object of something like oldMutableList.toList().
In other words, after a list is entered as a parameter in 'postvalue()'method of livedata, even if a new value is added to the list, livedata is not recognized. In order for the observer to recognize, the newly created list object must be entered as a parameter when calling postvalue again.
_liveData.postValue(list.toList()) // "list.toList()" <- this code generate new List object which has another hashcode.
or
_liveData.postValue(list.sortedBy(it.somethingField))
sorry about this kotlin code, not java.
// this original in your code
messageArrayList.postValue(cloneMessageList);
// change to these codes
messageArrayList.postValue(new ArrayList(cloneMessageList));
or
messageArrayList.postValue(cloneMessageList.toList());
My app fetches Top Rated or Most Popular movies from themoviedb.org.
I'm trying to implement ViewModel with LiveData to toggle these two buttons. In MainViewModel.java, I have this:
//private static long String TAG = MainViewModel.class.getSimpleName();
public LiveData<List<MovieRoom>> movies;
private AppDatabase database;
/*
Use constructor to initialize all data that UI needs to populate
*/
public MainViewModel(#NonNull Application application) {
super(application);
// how do I know this was initialized correctly?
database = AppDatabase.getInstance(this.getApplication());
//database = AppDatabase.getInstance(application);
//movies is null after calling ViewModelProvider constructor
// what are the values in movies?
// _movieDao = null, mDatabase = null
movies = database.movieDao().getAllMovies();
}
// Loads most popular movies
public void loadData() {
//MainViewModel.FetchMovieTask movies = new MainViewModel.FetchMovieTask();
//movies.execute("popular");
// Assign to movies
}
public void getAllMovies() {
movies = database.movieDao().getAllMovies();
}
public void getPopularMovies() {
movies = database.movieDao().getPopularMovies();
}
public void getTopRatedMovies() {
movies = database.movieDao().getTopRatedMovies();
}
public void getFavoriteMovies() {
movies = database.movieDao().loadFavoriteMovies(true);
}
}
In my MainActivity.java, the buttons have an onclick listener and I have set the observer as the following:
b_pop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//updateMovies("popular");
mViewModel.getPopularMovies();
}
});
mViewModel.movies.observe(this, new Observer<List<MovieRoom>>() {
#Override
public void onChanged(#Nullable List<MovieRoom> movieEntries) {
//Log.d(TAG, "Receiving database update from LiveData");
// mMovieAdapter.mMovies = 0
mMovieAdapter.clear();
// movieEntries = 0
// mMovieAdapter mMovies = 0
mMovieAdapter.setList(movieEntries);
mMovieAdapter.notifyDataSetChanged();
}
});
Problem is, when you reassign the ViewModel's "movies" variable with top rated or most popular list of movies, it doesn't trigger onChanged(). The Top Rated list of movies is the same list of movies as the most popular, but just in different order. So onChanged() is never triggered and I will not be able to set it to the adapter.
I'm learning ViewModel, LiveData and observer from 0 so not sure how to redesign this so that it can work?
Pass the value of newly fetched data to the ViewModel using value on your LiveData:
public void getAllMovies() {
movies.setValue(database.movieDao().getAllMovies().getValue())
}
Also you need MutableLiveData
public MutableLiveData<List<MovieRoom>> movies;
I want to display a list of match objects (match = two users having liked each other) in a recycler view with the help of an adapter.
This is my activity which is meant to display those matches:
public class MatchesActivity extends AppCompatActivity {
// variables:
private RecyclerView mMatchesRecyclerView;
private RecyclerView.Adapter mMatchItemAdapter;
private RecyclerView.LayoutManager mMatchesLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_matches);
mMatchesRecyclerView = findViewById(R.id.matches_recyclerView);
mMatchesRecyclerView.setNestedScrollingEnabled(false);
mMatchesRecyclerView.setHasFixedSize(true);
// set layout manager & pass it to the recycler view:
mMatchesLayoutManager = new LinearLayoutManager(MatchesActivity.this);
mMatchesRecyclerView.setLayoutManager(mMatchesLayoutManager);
// set match adapter & pass it to the recycler view:
mMatchItemAdapter = new MatchItemAdapter(getMatchesList(), MatchesActivity.this);
mMatchesRecyclerView.setAdapter(mMatchItemAdapter);
// add test items to the recycler view:
Match testMatch = new Match("abcdefgh");
matchesList.add(testMatch);
mMatchItemAdapter.notifyDataSetChanged();
Log.d("MatchesActivity", "TEST LIST: " + matchesList.toString());
}
private ArrayList<Match> matchesList = new ArrayList<Match>();
private List<Match> getMatchesList() {
Log.d("MatchesActivity", "getMatchesList function: " + matchesList.toString());
return matchesList;
}
}
And this is my adapter which is supposed to inflate the relevant layout & populate it with relevant object data:
public class MatchItemAdapter extends RecyclerView.Adapter<MatchViewholder> {
private List<Match> mMatchesList;
private Context mViewContext;
public MatchItemAdapter(List<Match> matchesList, Context context) {
this.mMatchesList = matchesList;
this.mViewContext = context;
Log.d("MatchItemAdapter", "Constructor: " + mMatchesList.toString());
}
// inflate the layout:
#Override
public MatchViewholder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_matches, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutView.setLayoutParams(lp);
MatchViewholder matchViewholder = new MatchViewholder(layoutView);
Log.d("MatchItemAdapter", "onCreateViewHolder: " + mMatchesList.toString());
return matchViewholder;
}
// populate each row within the layout:
#Override
public void onBindViewHolder(MatchViewholder holder, int position) {
Log.d("MatchItemAdapter", "onBindViewHolder: " + mMatchesList.toString());
holder.mMatchID.setText(mMatchesList.get(position).getMatchID());
}
#Override
public int getItemCount() {
return 0;
}
}
The Match class currently only takes matchID parameter which is string. An object is created with a default image and this matchID string.
At the moment, I have no real match objects from database ready, so I wanted to check that the recycler view along with adapter are working as expected before i move on to that later.
However, when I go to Matches Activity, it is empty, showing nothing at all. As you can see from the MatchesActivity onCreate method, I created a test Match object with matchID = "abcdefgh" and then added that to the matchesList. So I am expecting the "abcdefgh" text to be passed to the adapter and to be shown in the MatchesActivity.
My log statements indicate that the Match object has been created and added to the list successfully, however, getMatchesList() function returns an empty list which is then used in the Adapter constructor too, (I think this is) causing Activity not show anything.
I am relatively new to Android and Java development, especially recycler view and adapters, but from what I gathered it seems to be as if the
mMatchItemAdapter.notifyDataSetChanged();
is not working properly as everything seems to be fine up until that point. Any help would be appreciated!
You're returning 0. What you should do instead is return the length of the mMatchesList list.
#Override
public int getItemCount() {
return mMatchesList.size();
}
I made a AllertDialog that's supposed to display a list of apps, when I click on one of the alerdialog members it does return the right String from the ArrayAdapter.
Here's how it looks.
public class AppList {
private Activity main_activity;
private AlertDialog app_list_dialog;
private ArrayList<RemoteAppDetail> remoteAppDetails;
public AppList(Activity activity){
this.main_activity = activity;
// init();
}
/**
* Initialize the Dialog, this needs to be called before .show()/.hide().dispose();
*/
public void init(){
this.remoteAppDetails = ConnectionHandler.getLibrary();
AlertDialog.Builder app_list_builder = new AlertDialog.Builder(main_activity);
app_list_builder.setIcon(R.drawable.ic_input_add);
app_list_builder.setTitle("Installable/Updatable Apps: ");
final ArrayAdapter<String> app_list = new ArrayAdapter<String>(main_activity, R.layout.simple_list_item_single_choice);
final ArrayList<String> app_name_list = getInstallableApps();
app_list.addAll(app_name_list);
if(!app_list.isEmpty()){
//Add the app_list adapter (The button list)
app_list_builder.setAdapter(app_list, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Log.i("DIALOG SELECTED", app_list.getItem(which));
}
});
}else{
app_list_builder.setMessage("There are no apps available for download/update.");
}
//Close Button AppList
app_list_builder.setNegativeButton("Close",
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
this.app_list_dialog = app_list_builder.create();
}
/**
* Show the list.
*/
public void show(){
if(this.app_list_dialog != null){
app_list_dialog.show();
}
}
/**
* Hide the list.
*/
public void hide(){
if(this.app_list_dialog != null){
app_list_dialog.hide();
}
}
/**
* Disposes the list, it will need to be reinitialized of you change your mind after disposing of the list.
*/
public void dispose(){
if(this.app_list_dialog != null){
app_list_dialog.dismiss();
}
}
/**
* Gets a list of apps that can be installed
* #return Arraylist installable apps
*/
private ArrayList<String> getInstallableApps(){
ArrayList<String> installable_apps_list = new ArrayList<String>();
if(this.remoteAppDetails != null){
for(RemoteAppDetail rea : this.remoteAppDetails){
BasicNameValuePair remote_app_info = new BasicNameValuePair(rea.filename, String.valueOf(rea.version));
BasicNameValuePair local_app_info = getLocalAppInfo(rea.filename);
if(local_app_info != null){
if(isRemoteVersionNewer(remote_app_info, local_app_info)){
installable_apps_list.add(rea.filename);
}
}else{
if(rea.unlock_status == 0){
installable_apps_list.add(rea.filename);
}
}
}
}
return installable_apps_list;
}
/**
* Check if the app exists locally.
* #param app_name_remote
* #return Local app info as BasicNameValuePair
*/
private BasicNameValuePair getLocalAppInfo(String app_name_remote){
List<PackageInfo> packs = main_activity.getPackageManager().getInstalledPackages(0);
for(PackageInfo pi : packs){
String app_name_local = pi.applicationInfo.loadLabel(main_activity.getPackageManager()).toString();
String app_version = String.valueOf(pi.versionCode);
if(app_name_remote == app_name_local){
return new BasicNameValuePair(app_name_local, app_version);
}
}
return null;
}
/**
* Compare local and remote app info.
* #param remote_app_info
* #param local_app_info
* #return Return whether or not the remote version is newer as Boolean.
*/
private boolean isRemoteVersionNewer(BasicNameValuePair remote_app_info, BasicNameValuePair local_app_info){
if(remote_app_info.getName().contains(local_app_info.getName())){
if(remote_app_info.getValue() == local_app_info.getValue()){
return true;
}
}
return false;
}
So I would really like to know what I did wrong and if this is enough information.
UPDATE 1:
This is how I call this class:
Applist main_app_list = new Applist(this);
main_app_list.init();
main_app_list.show();
and it's called like that in my main activity's onCreate method.
Someone suggested my getInstallableApps() might return empty value's,
However if you inspect the code carefully this is not a possibility. Because if they there were empty it wouldn't even have any adapter members.
Either way I added some test value's, and it remains to do the exact same thing:
final ArrayAdapter<String> app_list = new ArrayAdapter<String>(main_activity, R.layout.simple_list_item_single_choice);
final ArrayList<String> app_name_list = getInstallableApps();
// app_list.addAll(app_name_list);
app_list.add("TEST");
app_list.add("TEST2");
The rest of the code remains the same.
UPDATE 2:
When I touch one of the members, it show's their name for a brief second, or when I hold it down it show's them for as long as I hold it.
Debug your getInstallableApps() method, you will see that it's entering the for statement because there are two elements in this.remoteAppDetails, but inside wether the elements you take data from are not called properly or empty (remote_app_info,local_app_info), or the filename field is empty. Once you find whats is placing empty values to you array you can fix it.
So I just ended up forcing the color to fix this since a custom layout was to much hassle for just this.
app_list = new ArrayAdapter<String>(main_activity, R.layout.simple_list_item_single_choice){
public View getView(int position, View convertView, android.view.ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(Color.BLACK);
return textView;
}
};
For future reference when you think it doesn't show adapter names, just hold down on one of the members to see if it show's the member name, if it does you know you got a font problem.