How do I - Create a List of Items - Transcript.pdf
1. How do I?
In this short video Iāll review the basics of creating a list of items in Codename One. But there is a catch, Codename One has a List class and we wonāt be using it. The list
class is designed for a very elaborate type of list that isnāt as useful for common day devices. Unless you have a very extreme and specific use case you probably
shouldnāt use it as it is slower & harder to use than the alternative!
2. Create a List Of Items
In this short video Iāll review the basics of creating a list of items in Codename One. But there is a catch, Codename One has a List class and we wonāt be using it. The list
class is designed for a very elaborate type of list that isnāt as useful for common day devices. Unless you have a very extreme and specific use case you probably
shouldnāt use it as it is slower & harder to use than the alternative!
4. Form hi = new Form("Basic List", new BorderLayout());
Container list = new Container(BoxLayout.y());
list.setScrollableY(true);
for(int iter = 0 ; iter < 1000 ; iter++) {
MultiButton mb = new MultiButton("List entry " + iter);
mb.setTextLine2("Further details....");
list.add(mb);
}
hi.add(CENTER, list);
hi.show();
Hello List
Lets start with a hello world for a box layout list. This is pretty standard Codename One code, lets go over the diļ¬erent pieces.
First we create the list container, notice we set it to scroll on the Y axis. This allows us to scroll through the list which is crucial. Notice that by default a Form is already
scrollable on the Y axis but Iāve set the layout to border layout which implicitly disables scrolling. Itās important that scrolling shouldnāt ānestā as itās impossible to pick the
right scrollbar with a touch interface.
5. Form hi = new Form("Basic List", new BorderLayout());
Container list = new Container(BoxLayout.y());
list.setScrollableY(true);
for(int iter = 0 ; iter < 1000 ; iter++) {
MultiButton mb = new MultiButton("List entry " + iter);
mb.setTextLine2("Further details....");
list.add(mb);
}
hi.add(CENTER, list);
hi.show();
Hello List
In this case I just added a thousand entries to the list one by one. The list is relatively simple with no actual functionality other than counting the entries
6. Form hi = new Form("Basic List", new BorderLayout());
Container list = new Container(BoxLayout.y());
list.setScrollableY(true);
for(int iter = 0 ; iter < 1000 ; iter++) {
MultiButton mb = new MultiButton("List entry " + iter);
mb.setTextLine2("Further details....");
list.add(mb);
}
hi.add(CENTER, list);
hi.show();
Hello List
Notice I place the list in the CENTER of the border layout. This is crucial as center in the border layout stretches the container to fit the screen exactly.
This is the resulting list that can be scrolled up and down by swiping.
7. Form hi = new Form("Infinite List", new BorderLayout());
Container list = new InfiniteContainer() {
@Override
public Component[] fetchComponents(int index, int amount) {
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
MultiButton mb = new MultiButton("List entry " + (index + iter));
mb.setTextLine2("Further details....");
more[iter] = mb;
}
return more;
}
};
hi.add(CENTER, list);
hi.show();
Infinite List
Thatās all good and well but in the real world we need lists to be more dynamic than that. We need to fetch data in batches. Thatās why we have the infinite container
class which allows us to fetch components as the user scrolls through the list. This is the exact same code from before but it creates the list entries in batches instead of
as a single block.
The fetchComponents method is invoked with an index in the list and the amount of elements to return. It then creates and returns those elements. This implicitly adds
the elements as we scroll and implements the pull to refresh functionality.
8. public Component[] fetchComponents(int index, int amount) {
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton("List entry " + offset);
mb.setTextLine2("Further details....");
FontImage.setMaterialIcon(mb, FontImage.MATERIAL_PERSON);
mb.addActionListener(ee ->
ToastBar.showMessage("Clicked: " + offset,
FontImage.MATERIAL_PERSON));
more[iter] = mb;
}
return more;
}
Events/Icons
This might be obvious but just in case you donāt know you can set an icon for every entry and just add an action listener to get events for a specific entry in the list. This
is very convenient.
This is relatively simple in terms of design, you can check out some of the more elaborate design of a list we have in the kitchen sink demo.
9. Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(FontImage.MATERIAL_PERSON, "Label", 6);
Container list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) // for pull for refresh by fetching the contacts over again
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(index + amount > contacts.length) {
amount = contacts.length - index;
if(amount <= 0) { // we reached the end of the infinite contacts
return null;
}
}
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton(contacts[offset].getDisplayName());
String contactId = contacts[iter].getId();
mb.setIcon(placeholderImage);
Display.getInstance().callSeriallyOnIdle(() -> {
Contact cnt=Display.getInstance().getContactById(contactId,false,true,false,false,false);
Image i = cnt.getPhoto();
if(i != null) mb.setIcon(i.fill(placeholderImage.getWidth(), placeholderImage.getHeight()));
});
more[iter] = mb;
}
Contacts List
Up until now we did simple demos, this is a screenshot of a contacts list from my device using this sort of API and it was generated with this code. Notice I blurred a few
entries since these are my actual phone contacts and Iād like to keep their privacyā¦ This is done with the code here, letās go over it.
10. Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(FontImage.MATERIAL_PERSON, "Label", 6);
Container list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) // for pull for refresh by fetching the contacts over again
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(index + amount > contacts.length) {
amount = contacts.length - index;
if(amount <= 0) { // we reached the end of the infinite contacts
return null;
}
}
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton(contacts[offset].getDisplayName());
String contactId = contacts[iter].getId();
mb.setIcon(placeholderImage);
Display.getInstance().callSeriallyOnIdle(() -> {
Contact cnt=Display.getInstance().getContactById(contactId,false,true,false,false,false);
Image i = cnt.getPhoto();
if(i != null) mb.setIcon(i.fill(placeholderImage.getWidth(), placeholderImage.getHeight()));
});
more[iter] = mb;
}
Contacts List
First we need a placeholder image for the common case where a contact doesnāt have a profile picture or when we are still loading the profile picture.
11. Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(FontImage.MATERIAL_PERSON, "Label", 6);
Container list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) // for pull for refresh by fetching the contacts over again
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(index + amount > contacts.length) {
amount = contacts.length - index;
if(amount <= 0) { // we reached the end of the infinite contacts
return null;
}
}
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton(contacts[offset].getDisplayName());
String contactId = contacts[iter].getId();
mb.setIcon(placeholderImage);
Display.getInstance().callSeriallyOnIdle(() -> {
Contact cnt=Display.getInstance().getContactById(contactId,false,true,false,false,false);
Image i = cnt.getPhoto();
if(i != null) mb.setIcon(i.fill(placeholderImage.getWidth(), placeholderImage.getHeight()));
});
more[iter] = mb;
}
Contacts List
When we are in the first element which will happen when the form loads or after a āpull to refreshā I load the contacts. Notice I could have used if contacts equals null but
that would have done nothing in the case of pull to refresh as contacts would have been initialized already. By checking against zero I implicitly support the pull to refresh
behavior which just calls the fetch method over again.
The contacts API can be a bit slow sometimes which is why you shouldnāt fetch āeverythingā with one request. Thatās why the method accepts all of these boolean values
to indicate what we need from the contact itself. Setting all of these to true will slow you down significantly so you should generally load just what you need which in this
case is contacts with a phone number and full name.
12. Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(FontImage.MATERIAL_PERSON, "Label", 6);
Container list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) // for pull for refresh by fetching the contacts over again
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(index + amount > contacts.length) {
amount = contacts.length - index;
if(amount <= 0) { // we reached the end of the infinite contacts
return null;
}
}
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton(contacts[offset].getDisplayName());
String contactId = contacts[iter].getId();
mb.setIcon(placeholderImage);
Display.getInstance().callSeriallyOnIdle(() -> {
Contact cnt=Display.getInstance().getContactById(contactId,false,true,false,false,false);
Image i = cnt.getPhoto();
if(i != null) mb.setIcon(i.fill(placeholderImage.getWidth(), placeholderImage.getHeight()));
});
more[iter] = mb;
}
Contacts List
The infinite container has no idea how many elements we might have. So we need to check if the amount of elements requested exceeds the total and if the index is out
of bounds. If the former is true we need to reduce the amount and return a smaller array. If the latter is true we need to return null which will stop future calls to fetch
components unless pull to refresh is triggered again.
13. Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(FontImage.MATERIAL_PERSON, "Label", 6);
Container list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) // for pull for refresh by fetching the contacts over again
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(index + amount > contacts.length) {
amount = contacts.length - index;
if(amount <= 0) { // we reached the end of the infinite contacts
return null;
}
}
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton(contacts[offset].getDisplayName());
String contactId = contacts[iter].getId();
mb.setIcon(placeholderImage);
Display.getInstance().callSeriallyOnIdle(() -> {
Contact cnt=Display.getInstance().getContactById(contactId,false,true,false,false,false);
Image i = cnt.getPhoto();
if(i != null) mb.setIcon(i.fill(placeholderImage.getWidth(), placeholderImage.getHeight()));
});
more[iter] = mb;
}
Contacts List
The rest is pretty close to the code we had before where we loop and create multi buttons but in this case we just fill them up with the content details and the placeholder
image.
14. Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(FontImage.MATERIAL_PERSON, "Label", 6);
Container list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) // for pull for refresh by fetching the contacts over again
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(index + amount > contacts.length) {
amount = contacts.length - index;
if(amount <= 0) { // we reached the end of the infinite contacts
return null;
}
}
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton(contacts[offset].getDisplayName());
String contactId = contacts[iter].getId();
mb.setIcon(placeholderImage);
Display.getInstance().callSeriallyOnIdle(() -> {
Contact cnt=Display.getInstance().getContactById(contactId,false,true,false,false,false);
Image i = cnt.getPhoto();
if(i != null) mb.setIcon(i.fill(placeholderImage.getWidth(), placeholderImage.getHeight()));
});
more[iter] = mb;
}
Contacts List
However, you might recall we didnāt fetch the image for the contact and that might be pretty expensive to loadā¦ So the trick is to call this method on a button by button
case where we fetch ONLY the image but we donāt just invoke that as it would kill performance.
For this we use the new callSeriallyOnIdle() method. This method works like callSerially by performing the code in the next event dispatch thread cycle. However, in this
case the code will only occur when the phone is idle and there are no other urgent events.
15. Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(FontImage.MATERIAL_PERSON, "Label", 6);
Container list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) // for pull for refresh by fetching the contacts over again
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(index + amount > contacts.length) {
amount = contacts.length - index;
if(amount <= 0) { // we reached the end of the infinite contacts
return null;
}
}
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton(contacts[offset].getDisplayName());
String contactId = contacts[iter].getId();
mb.setIcon(placeholderImage);
Display.getInstance().callSeriallyOnIdle(() -> {
Contact cnt=Display.getInstance().getContactById(contactId,false,true,false,false,false);
Image i = cnt.getPhoto();
if(i != null) mb.setIcon(i.fill(placeholderImage.getWidth(), placeholderImage.getHeight()));
});
more[iter] = mb;
}
Contacts List
So if we are in idle state we can just ask for the contacts image using the specific API then fill up the UI. Since this is an infinite list this will only be invoked for the
āamountā number of entries per cycle and that means it should be reasonably eļ¬cient.
16. Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton(contacts[offset].getDisplayName());
String contactId = contacts[iter].getId();
mb.setIcon(placeholderImage);
Display.getInstance().callSeriallyOnIdle(() -> {
Contact cnt=Display.getInstance().getContactById(contactId,true,true,false,false,false);
Image i = cnt.getPhoto();
if(i != null) mb.setIcon(i.fill(placeholderImage.getWidth(), placeholderImage.getHeight()));
});
more[iter] = mb;
}
return more;
}
};
hi.add(CENTER, list);
hi.show();
Contacts List
Moving to the next page we can see that not much is left, we just return the array and add the list to the center.
I didnāt spend much time on refinement and some of the nicer eļ¬ects you can achieve but you can check out the kitchen sink demo where the contacts section features
a swipe UI with many special eļ¬ects such as generated icons per letter.
17. String searchString;
public void start() {
if(current != null){
current.show();
return;
}
Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(
FontImage.MATERIAL_PERSON, "Label", 6);
InfiniteContainer list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) {
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(searchString != null && searchString.length() > 0) {
ArrayList<Contact> cts = new ArrayList<>();
searchString = searchString.toLowerCase();
for(Contact c : contacts) {
if(c.getDisplayName().toLowerCase().indexOf(searchString) > -1) {
cts.add(c);
}
}
contacts = new Contact[cts.size()];
cts.toArray(contacts);
}
}
if(index + amount > contacts.length) {
amount = contacts.length - index;
if(amount <= 0) { // we reached the end of the infinite contacts
return null;
}
}
Component[] more = new Component[amount];
for(int iter = 0 ; iter < amount ; iter++) {
int offset = index + iter;
MultiButton mb = new MultiButton(contacts[offset].getDisplayName());
String contactId = contacts[iter].getId();
mb.setIcon(placeholderImage);
Display.getInstance().callSeriallyOnIdle(() -> {
Contact cnt=Display.getInstance().getContactById(contactId,true,true,false,false,false);
Image i = cnt.getPhoto();
if(i != null) mb.setIcon(i.fill(placeholderImage.getWidth(), placeholderImage.getHeight()));
});
more[iter] = mb;
}
return more;
}
};
hi.getToolbar().addSearchCommand(e -> {
searchString = (String)e.getSource();
list.refresh();
});
hi.add(CENTER, list);
hi.show();
Search
Adding search to the infinite list is pretty easy
18. String searchString;
public void start() {
if(current != null){
current.show();
return;
}
Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(
FontImage.MATERIAL_PERSON, "Label", 6);
InfiniteContainer list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) {
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(searchString != null && searchString.length() > 0) {
ArrayList<Contact> cts = new ArrayList<>();
searchString = searchString.toLowerCase();
for(Contact c : contacts) {
if(c.getDisplayName().toLowerCase().indexOf(searchString) > -1) {
cts.add(c);
}
}
contacts = new Contact[cts.size()];
cts.toArray(contacts);
}
}
Search
We need a search string variable that is both modifiable and accessible in the inner class so I added a member to the parent class.
19. String searchString;
public void start() {
if(current != null){
current.show();
return;
}
Form hi = new Form("Contacts List", new BorderLayout());
final FontImage placeholderImage = FontImage.createMaterial(
FontImage.MATERIAL_PERSON, "Label", 6);
InfiniteContainer list = new InfiniteContainer() {
private Contact[] contacts;
public Component[] fetchComponents(int index, int amount) {
if(index == 0) {
contacts = Display.getInstance().getAllContacts(true, true, false, false, false, false);
if(searchString != null && searchString.length() > 0) {
ArrayList<Contact> cts = new ArrayList<>();
searchString = searchString.toLowerCase();
for(Contact c : contacts) {
if(c.getDisplayName().toLowerCase().indexOf(searchString) > -1) {
cts.add(c);
}
}
contacts = new Contact[cts.size()];
cts.toArray(contacts);
}
}
Search
The rest of the code is identical, I just filter the basic contacts entry. A change in search will refresh the list and invoke fetch components for index zero. If in this case I
have a search filter I can loop over the contacts and filter them into a new array list. I can then create a new smaller contacts array that matches the search.
20. hi.getToolbar().addSearchCommand(e -> {
searchString = (String)e.getSource();
list.refresh();
});
hi.add(CENTER, list);
hi.show();
}
Search
The search string can be bound to the variable using the toolbar search API thatās builtin. The call to refresh has a similar eļ¬ect to pull to refresh. It allows me to filter the
list dynamically and eļ¬ciently.