Creating a Whatsapp Clone - Part III - Transcript.pdf
1. Creating a WhatsApp Clone - Part III
The next step is the other classes within the model package. The rest is relatively trivial after the Server class.
2. package com.codename1.whatsapp.model;
import java.util.List;
public interface ServerMessages {
public void connected();
public void disconnected();
public void messageReceived(ChatMessage m);
public void userTyping(String contactId);
public void messageViewed(String messageId, List<String> userIds);
}
ServerMessages
This is the callback interface we used within the Server class, it’s pretty trivial. I added some methods for future enhancement too.
The first two methods inform the observer that the server is connected or disconnected.
3. package com.codename1.whatsapp.model;
import java.util.List;
public interface ServerMessages {
public void connected();
public void disconnected();
public void messageReceived(ChatMessage m);
public void userTyping(String contactId);
public void messageViewed(String messageId, List<String> userIds);
}
ServerMessages
Message received is invoked to update the UI based on the new incoming message
4. package com.codename1.whatsapp.model;
import java.util.List;
public interface ServerMessages {
public void connected();
public void disconnected();
public void messageReceived(ChatMessage m);
public void userTyping(String contactId);
public void messageViewed(String messageId, List<String> userIds);
}
ServerMessages
The last two callbacks aren’t really implemented but they allow us to update the UI if a user is typing in a chat. The message viewed event similarly indicates if a user
viewed the message. This can provide an indicator in the UI that the message was seen.
5. public class ChatContact implements PropertyBusinessObject {
public final Property<String, ChatContact> id = new Property<>("id");
public final Property<String, ChatContact> localId =
new Property<>("localId");
public final Property<String, ChatContact> phone =
new Property<>("phone");
public final Property<Image, ChatContact> photo =
new Property<>("photo", Image.class);
public final Property<String, ChatContact> name =
new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
ChatContact
ChatContact is a PropertyBusinessObject that stores the content of a specific contact entry.
6. public class ChatContact implements PropertyBusinessObject {
public final Property<String, ChatContact> id = new Property<>("id");
public final Property<String, ChatContact> localId =
new Property<>("localId");
public final Property<String, ChatContact> phone =
new Property<>("phone");
public final Property<Image, ChatContact> photo =
new Property<>("photo", Image.class);
public final Property<String, ChatContact> name =
new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
ChatContact
I chose to use unique id’s instead of using the phone as an id. This was something I was conflicted about. I eventually chose to use an ID which I think is more secure
overall. It would also support the option of changing a phone number in the future or using an email as the unique identifier
7. public class ChatContact implements PropertyBusinessObject {
public final Property<String, ChatContact> id = new Property<>("id");
public final Property<String, ChatContact> localId =
new Property<>("localId");
public final Property<String, ChatContact> phone =
new Property<>("phone");
public final Property<Image, ChatContact> photo =
new Property<>("photo", Image.class);
public final Property<String, ChatContact> name =
new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
ChatContact
localId should map to the ID in the contacts. This allows us to refresh the contact details from the device address book in the future.
8. public class ChatContact implements PropertyBusinessObject {
public final Property<String, ChatContact> id = new Property<>("id");
public final Property<String, ChatContact> localId =
new Property<>("localId");
public final Property<String, ChatContact> phone =
new Property<>("phone");
public final Property<Image, ChatContact> photo =
new Property<>("photo", Image.class);
public final Property<String, ChatContact> name =
new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
ChatContact
The phone property is pretty obvious
9. public class ChatContact implements PropertyBusinessObject {
public final Property<String, ChatContact> id = new Property<>("id");
public final Property<String, ChatContact> localId =
new Property<>("localId");
public final Property<String, ChatContact> phone =
new Property<>("phone");
public final Property<Image, ChatContact> photo =
new Property<>("photo", Image.class);
public final Property<String, ChatContact> name =
new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
ChatContact
The photo property stores the picture of the contact, there is a lot to this property so I’ll discuss it in more detail soon.
10. public class ChatContact implements PropertyBusinessObject {
public final Property<String, ChatContact> id = new Property<>("id");
public final Property<String, ChatContact> localId =
new Property<>("localId");
public final Property<String, ChatContact> phone =
new Property<>("phone");
public final Property<Image, ChatContact> photo =
new Property<>("photo", Image.class);
public final Property<String, ChatContact> name =
new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
ChatContact
These are the common attributes for name and tagline used in whatsapp. For simplicity I only used full name and ignored nuances such as first/last/middle etc.
11. public class ChatContact implements PropertyBusinessObject {
public final Property<String, ChatContact> id = new Property<>("id");
public final Property<String, ChatContact> localId =
new Property<>("localId");
public final Property<String, ChatContact> phone =
new Property<>("phone");
public final Property<Image, ChatContact> photo =
new Property<>("photo", Image.class);
public final Property<String, ChatContact> name =
new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
ChatContact
The token is effectively our password to use the service. Since there is no login process a token is generated on the server as a key that allows us to use the service.
12. new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
public final Property<String, ChatContact> createdBy =
new Property<>("createdBy");
public final Property<Date, ChatContact> creationDate =
new Property<>("creationDate", Date.class);
public final Property<Date, ChatContact> lastActivityTime =
new Property<>("lastActivityTime", Date.class);
public final ListProperty<ChatMessage, ChatContact> chats =
new ListProperty<>("chat", ChatMessage.class);
private final PropertyIndex idx = new PropertyIndex(this, "ChatContact",
id, localId, phone, photo, name, tagline, token, members, admins,
muteUntil, createdBy, creationDate, lastActivityTime, chats);
ChatContact
A chat contact can also serve as a group. I didn’t fully implement this logic but it’s wired almost everywhere. In this case we have two sets for members of the group and
the admins of the group. These sets would be empty for a typical user.
13. new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
public final Property<String, ChatContact> createdBy =
new Property<>("createdBy");
public final Property<Date, ChatContact> creationDate =
new Property<>("creationDate", Date.class);
public final Property<Date, ChatContact> lastActivityTime =
new Property<>("lastActivityTime", Date.class);
public final ListProperty<ChatMessage, ChatContact> chats =
new ListProperty<>("chat", ChatMessage.class);
private final PropertyIndex idx = new PropertyIndex(this, "ChatContact",
id, localId, phone, photo, name, tagline, token, members, admins,
muteUntil, createdBy, creationDate, lastActivityTime, chats);
ChatContact
This property allows us to mute a chat contact so we won’t see notifications from that contact
14. new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
public final Property<String, ChatContact> createdBy =
new Property<>("createdBy");
public final Property<Date, ChatContact> creationDate =
new Property<>("creationDate", Date.class);
public final Property<Date, ChatContact> lastActivityTime =
new Property<>("lastActivityTime", Date.class);
public final ListProperty<ChatMessage, ChatContact> chats =
new ListProperty<>("chat", ChatMessage.class);
private final PropertyIndex idx = new PropertyIndex(this, "ChatContact",
id, localId, phone, photo, name, tagline, token, members, admins,
muteUntil, createdBy, creationDate, lastActivityTime, chats);
ChatContact
If this is a group then it was created by a specific user. The id of that user should be listed here
15. new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
public final Property<String, ChatContact> createdBy =
new Property<>("createdBy");
public final Property<Date, ChatContact> creationDate =
new Property<>("creationDate", Date.class);
public final Property<Date, ChatContact> lastActivityTime =
new Property<>("lastActivityTime", Date.class);
public final ListProperty<ChatMessage, ChatContact> chats =
new ListProperty<>("chat", ChatMessage.class);
private final PropertyIndex idx = new PropertyIndex(this, "ChatContact",
id, localId, phone, photo, name, tagline, token, members, admins,
muteUntil, createdBy, creationDate, lastActivityTime, chats);
ChatContact
The creation date is applicable to both groups and individual users
16. new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
public final Property<String, ChatContact> createdBy =
new Property<>("createdBy");
public final Property<Date, ChatContact> creationDate =
new Property<>("creationDate", Date.class);
public final Property<Date, ChatContact> lastActivityTime =
new Property<>("lastActivityTime", Date.class);
public final ListProperty<ChatMessage, ChatContact> chats =
new ListProperty<>("chat", ChatMessage.class);
private final PropertyIndex idx = new PropertyIndex(this, "ChatContact",
id, localId, phone, photo, name, tagline, token, members, admins,
muteUntil, createdBy, creationDate, lastActivityTime, chats);
ChatContact
This is the timestamp of the last message we received from the given user. We saw this updated in the Server class. We use this to sort the chats by latest update
17. new Property<>("name");
public final Property<String, ChatContact> tagline =
new Property<>("tagline");
public final Property<String, ChatContact> token =
new Property<>("token");
public final SetProperty<String, ChatContact> members =
new SetProperty<>("members", String.class);
public final SetProperty<String, ChatContact> admins =
new SetProperty<>("admins", String.class);
public final LongProperty<ChatContact> muteUntil =
new LongProperty<>("muteUntil");
public final Property<String, ChatContact> createdBy =
new Property<>("createdBy");
public final Property<Date, ChatContact> creationDate =
new Property<>("creationDate", Date.class);
public final Property<Date, ChatContact> lastActivityTime =
new Property<>("lastActivityTime", Date.class);
public final ListProperty<ChatMessage, ChatContact> chats =
new ListProperty<>("chat", ChatMessage.class);
private final PropertyIndex idx = new PropertyIndex(this, "ChatContact",
id, localId, phone, photo, name, tagline, token, members, admins,
muteUntil, createdBy, creationDate, lastActivityTime, chats);
ChatContact
ChatMessage is the property business object that contains the content of the message. Here we store the actual chats we had with the contact or group.
18. new Property<>("createdBy");
public final Property<Date, ChatContact> creationDate =
new Property<>("creationDate", Date.class);
public final Property<Date, ChatContact> lastActivityTime =
new Property<>("lastActivityTime", Date.class);
public final ListProperty<ChatMessage, ChatContact> chats =
new ListProperty<>("chat", ChatMessage.class);
private final PropertyIndex idx = new PropertyIndex(this, "ChatContact",
id, localId, phone, photo, name, tagline, token, members, admins,
muteUntil, createdBy, creationDate, lastActivityTime, chats);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
public ChatContact() {
idx.setExcludeFromJSON(photo, true);
}
private static final int SMALL_IMAGE = 0;
private static final int LARGE_IMAGE = 1;
private static final float[] IMAGE_SIZES = {4f, 6.5f};
ChatContact
As I mentioned before photo isn’t stored in JSON when we save the contact to keep the size low. We save the contact image in a separate file and don't want too much
noise here.
19. return idx;
}
public ChatContact() {
idx.setExcludeFromJSON(photo, true);
}
private static final int SMALL_IMAGE = 0;
private static final int LARGE_IMAGE = 1;
private static final float[] IMAGE_SIZES = {4f, 6.5f};
private static final Image[] maskImage = new Image[2];
private static final Object[] maskObject = new Object[2];
private static final EncodedImage[] placeholder = new EncodedImage[2];
private static Image createMaskImage(int size) {
Image m = Image.createImage(size, size, 0);
Graphics g = m.getGraphics();
g.setAntiAliased(true);
g.setColor(0xffffff);
g.fillArc(0, 0, size - 1, size -1, 0, 360);
return m;
}
private Image createPlaceholder(int size) {
ChatContact
The app has two thumbnail images. One is slightly smaller than the other and both are rounded. To keep the code generic I use arrays with the detail and then use two
sizes. The small size maps to the 0 offset in the array and the large size maps to the one offset.
20. return idx;
}
public ChatContact() {
idx.setExcludeFromJSON(photo, true);
}
private static final int SMALL_IMAGE = 0;
private static final int LARGE_IMAGE = 1;
private static final float[] IMAGE_SIZES = {4f, 6.5f};
private static final Image[] maskImage = new Image[2];
private static final Object[] maskObject = new Object[2];
private static final EncodedImage[] placeholder = new EncodedImage[2];
private static Image createMaskImage(int size) {
Image m = Image.createImage(size, size, 0);
Graphics g = m.getGraphics();
g.setAntiAliased(true);
g.setColor(0xffffff);
g.fillArc(0, 0, size - 1, size -1, 0, 360);
return m;
}
private Image createPlaceholder(int size) {
ChatContact
Here are the sizes of these two images in millimeters
21. return idx;
}
public ChatContact() {
idx.setExcludeFromJSON(photo, true);
}
private static final int SMALL_IMAGE = 0;
private static final int LARGE_IMAGE = 1;
private static final float[] IMAGE_SIZES = {4f, 6.5f};
private static final Image[] maskImage = new Image[2];
private static final Object[] maskObject = new Object[2];
private static final EncodedImage[] placeholder = new EncodedImage[2];
private static Image createMaskImage(int size) {
Image m = Image.createImage(size, size, 0);
Graphics g = m.getGraphics();
g.setAntiAliased(true);
g.setColor(0xffffff);
g.fillArc(0, 0, size - 1, size -1, 0, 360);
return m;
}
private Image createPlaceholder(int size) {
ChatContact
Images are masked to these sizes, masking allows us to round an image in this case
22. return idx;
}
public ChatContact() {
idx.setExcludeFromJSON(photo, true);
}
private static final int SMALL_IMAGE = 0;
private static final int LARGE_IMAGE = 1;
private static final float[] IMAGE_SIZES = {4f, 6.5f};
private static final Image[] maskImage = new Image[2];
private static final Object[] maskObject = new Object[2];
private static final EncodedImage[] placeholder = new EncodedImage[2];
private static Image createMaskImage(int size) {
Image m = Image.createImage(size, size, 0);
Graphics g = m.getGraphics();
g.setAntiAliased(true);
g.setColor(0xffffff);
g.fillArc(0, 0, size - 1, size -1, 0, 360);
return m;
}
private Image createPlaceholder(int size) {
ChatContact
We generate placeholder images which are used when an image is unavailable
23. return idx;
}
public ChatContact() {
idx.setExcludeFromJSON(photo, true);
}
private static final int SMALL_IMAGE = 0;
private static final int LARGE_IMAGE = 1;
private static final float[] IMAGE_SIZES = {4f, 6.5f};
private static final Image[] maskImage = new Image[2];
private static final Object[] maskObject = new Object[2];
private static final EncodedImage[] placeholder = new EncodedImage[2];
private static Image createMaskImage(int size) {
Image m = Image.createImage(size, size, 0);
Graphics g = m.getGraphics();
g.setAntiAliased(true);
g.setColor(0xffffff);
g.fillArc(0, 0, size - 1, size -1, 0, 360);
return m;
}
private Image createPlaceholder(int size) {
ChatContact
This method creates a mask image of a given size in pixels a mask image uses black pixels to represent transparency and white pixels to represent the visible opacity. So
where we draw a black rectangle image with a white circle in the center. When we apply this mask to an image only the portion represented by the white circle will
remain.
24. Graphics g = m.getGraphics();
g.setAntiAliased(true);
g.setColor(0xffffff);
g.fillArc(0, 0, size - 1, size -1, 0, 360);
return m;
}
private Image createPlaceholder(int size) {
Image i = Image.createImage(size, size, 0xffe2e7ea);
Graphics g = i.getGraphics();
g.setAntiAliased(true);
g.setAntiAliasedText(true);
g.setColor(0xffffff);
Font fnt = FontImage.getMaterialDesignFont().
derive(size, Font.STYLE_PLAIN);
g.setFont(fnt);
String s = "" + FontImage.MATERIAL_PERSON;
g.drawString(s, size / 2 - fnt.stringWidth(s) / 2, 0);
return i;
}
private Image getImage(int offset) {
if(maskImage[offset] == null) {
ChatContact
The placeholder image is used when no image is defined, again we create this based on a size in pixels. We create a gray image then draw on it using white. We use the
material font to draw the image of a person onto this image
25. g.drawString(s, size / 2 - fnt.stringWidth(s) / 2, 0);
return i;
}
private Image getImage(int offset) {
if(maskImage[offset] == null) {
maskImage[offset] =
createMaskImage(convertToPixels(IMAGE_SIZES[offset]));
maskObject[offset] = maskImage[offset].createMask();
Image i = createPlaceholder(
convertToPixels(IMAGE_SIZES[offset]));
placeholder[offset] = EncodedImage.createFromImage(
i.applyMask(maskObject[offset]), false);
}
if(photo.get() == null) {
return placeholder[offset];
}
return photo.get().
fill(maskImage[offset].getWidth(),
maskImage[offset].getHeight()).
applyMask(maskObject[offset]);
}
ChatContact
This method gets the image represented by the contact. In theory I could have used the photo property and overridden get() to implement this. I thought this is a simpler
approach.
26. g.drawString(s, size / 2 - fnt.stringWidth(s) / 2, 0);
return i;
}
private Image getImage(int offset) {
if(maskImage[offset] == null) {
maskImage[offset] =
createMaskImage(convertToPixels(IMAGE_SIZES[offset]));
maskObject[offset] = maskImage[offset].createMask();
Image i = createPlaceholder(
convertToPixels(IMAGE_SIZES[offset]));
placeholder[offset] = EncodedImage.createFromImage(
i.applyMask(maskObject[offset]), false);
}
if(photo.get() == null) {
return placeholder[offset];
}
return photo.get().
fill(maskImage[offset].getWidth(),
maskImage[offset].getHeight()).
applyMask(maskObject[offset]);
}
ChatContact
Here we lazily initialize the arrays of the mask images for large or small images.
27. g.drawString(s, size / 2 - fnt.stringWidth(s) / 2, 0);
return i;
}
private Image getImage(int offset) {
if(maskImage[offset] == null) {
maskImage[offset] =
createMaskImage(convertToPixels(IMAGE_SIZES[offset]));
maskObject[offset] = maskImage[offset].createMask();
Image i = createPlaceholder(
convertToPixels(IMAGE_SIZES[offset]));
placeholder[offset] = EncodedImage.createFromImage(
i.applyMask(maskObject[offset]), false);
}
if(photo.get() == null) {
return placeholder[offset];
}
return photo.get().
fill(maskImage[offset].getWidth(),
maskImage[offset].getHeight()).
applyMask(maskObject[offset]);
}
ChatContact
We create the mask images then convert the mask image to mask object. Finally we create the placeholder image
28. g.drawString(s, size / 2 - fnt.stringWidth(s) / 2, 0);
return i;
}
private Image getImage(int offset) {
if(maskImage[offset] == null) {
maskImage[offset] =
createMaskImage(convertToPixels(IMAGE_SIZES[offset]));
maskObject[offset] = maskImage[offset].createMask();
Image i = createPlaceholder(
convertToPixels(IMAGE_SIZES[offset]));
placeholder[offset] = EncodedImage.createFromImage(
i.applyMask(maskObject[offset]), false);
}
if(photo.get() == null) {
return placeholder[offset];
}
return photo.get().
fill(maskImage[offset].getWidth(),
maskImage[offset].getHeight()).
applyMask(maskObject[offset]);
}
ChatContact
If the photo is null I return the placeholder image instead of using the photo object
29. g.drawString(s, size / 2 - fnt.stringWidth(s) / 2, 0);
return i;
}
private Image getImage(int offset) {
if(maskImage[offset] == null) {
maskImage[offset] =
createMaskImage(convertToPixels(IMAGE_SIZES[offset]));
maskObject[offset] = maskImage[offset].createMask();
Image i = createPlaceholder(
convertToPixels(IMAGE_SIZES[offset]));
placeholder[offset] = EncodedImage.createFromImage(
i.applyMask(maskObject[offset]), false);
}
if(photo.get() == null) {
return placeholder[offset];
}
return photo.get().
fill(maskImage[offset].getWidth(),
maskImage[offset].getHeight()).
applyMask(maskObject[offset]);
}
ChatContact
Otherwise we fill the image into the size of the mask and apply the mask to create a round object. Fill scales the image so it’s cropped while filling the exact boundaries
given. It doesn’t distort the aspect ratio of the image like a typical scale operation would
30. applyMask(maskObject[offset]);
}
private Image smallIcon;
private Image largeIcon;
public Image getSmallIcon() {
if(smallIcon != null) {
return smallIcon;
}
smallIcon = getImage(SMALL_IMAGE);
return smallIcon;
}
public Image getLargeIcon() {
if(largeIcon != null) {
return largeIcon;
}
largeIcon = getImage(LARGE_IMAGE);
return largeIcon;
}
}
ChatContact
The final public methods and variables cache the small & large image. Appropriately they are the publicly exposed API’s for this functionality