Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Creating a Facebook Clone - Part XIV - Transcript.pdf
1. Creating a Facebook Clone - Part XIV
The notifications container lists alerts received by the app. It's relatively simple.
2. public class Notification implements PropertyBusinessObject {
public final Property<String, Notification> id = new Property<>("id");
public final Property<User, Notification> user =
new Property<>("user", User.class);
public final Property<String, Notification> text =
new Property<>("text");
public final Property<String, Notification> reaction =
new Property<>("reaction");
public final IntProperty<Notification> reactionColor =
new IntProperty<>("reactionColor");
public final LongProperty<Notification> date =
new LongProperty<>("date");
public final BooleanProperty<Notification> wasRead =
new BooleanProperty<>("wasRead");
private final PropertyIndex idx=new PropertyIndex(this,"Notification",
id, user, text, reaction, reactionColor, date, wasRead);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
}
FriendsContainer
To support this page we'll need to add a few things into the data model. First we need to add a new Notification business object to the data package.
3. public class Notification implements PropertyBusinessObject {
public final Property<String, Notification> id = new Property<>("id");
public final Property<User, Notification> user =
new Property<>("user", User.class);
public final Property<String, Notification> text =
new Property<>("text");
public final Property<String, Notification> reaction =
new Property<>("reaction");
public final IntProperty<Notification> reactionColor =
new IntProperty<>("reactionColor");
public final LongProperty<Notification> date =
new LongProperty<>("date");
public final BooleanProperty<Notification> wasRead =
new BooleanProperty<>("wasRead");
private final PropertyIndex idx=new PropertyIndex(this,"Notification",
id, user, text, reaction, reactionColor, date, wasRead);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
}
FriendsContainer
The text of the notification should arrive from the server
4. public class Notification implements PropertyBusinessObject {
public final Property<String, Notification> id = new Property<>("id");
public final Property<User, Notification> user =
new Property<>("user", User.class);
public final Property<String, Notification> text =
new Property<>("text");
public final Property<String, Notification> reaction =
new Property<>("reaction");
public final IntProperty<Notification> reactionColor =
new IntProperty<>("reactionColor");
public final LongProperty<Notification> date =
new LongProperty<>("date");
public final BooleanProperty<Notification> wasRead =
new BooleanProperty<>("wasRead");
private final PropertyIndex idx=new PropertyIndex(this,"Notification",
id, user, text, reaction, reactionColor, date, wasRead);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
}
FriendsContainer
Reaction is the material icon associated with the notification e.g. a heart or thumbUp
5. public class Notification implements PropertyBusinessObject {
public final Property<String, Notification> id = new Property<>("id");
public final Property<User, Notification> user =
new Property<>("user", User.class);
public final Property<String, Notification> text =
new Property<>("text");
public final Property<String, Notification> reaction =
new Property<>("reaction");
public final IntProperty<Notification> reactionColor =
new IntProperty<>("reactionColor");
public final LongProperty<Notification> date =
new LongProperty<>("date");
public final BooleanProperty<Notification> wasRead =
new BooleanProperty<>("wasRead");
private final PropertyIndex idx=new PropertyIndex(this,"Notification",
id, user, text, reaction, reactionColor, date, wasRead);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
}
FriendsContainer
The background color can also come from the server as the original UI has different color for every notification reaction. This is all pretty simple.
6. return null;
}
public static List<Notification> fetchNotifications(
long since, int amount) {
if(since < initTime - UIUtils.DAY) {
return null;
}
List<Notification> response = new ArrayList<>();
response.add(new Notification().id.set("Notify-1").
user.set(dummyUsers[0]).
text.set("liked Your Post").
date.set(initTime - 60000).
reaction.set("" + FontImage.MATERIAL_FAVORITE).
reactionColor.set(0xff0000));
response.add(new Notification().id.set("Notify-2").
user.set(dummyUsers[1]).
text.set("commented on your post").
date.set(initTime - 600000000).
reaction.set("" + FontImage.MATERIAL_CHAT).
reactionColor.set(0xff00));
return response;
}
}
ServerAPI
To mock this in the UI we'll need a new method in the ServerAPI class
7. return null;
}
public static List<Notification> fetchNotifications(
long since, int amount) {
if(since < initTime - UIUtils.DAY) {
return null;
}
List<Notification> response = new ArrayList<>();
response.add(new Notification().id.set("Notify-1").
user.set(dummyUsers[0]).
text.set("liked Your Post").
date.set(initTime - 60000).
reaction.set("" + FontImage.MATERIAL_FAVORITE).
reactionColor.set(0xff0000));
response.add(new Notification().id.set("Notify-2").
user.set(dummyUsers[1]).
text.set("commented on your post").
date.set(initTime - 600000000).
reaction.set("" + FontImage.MATERIAL_CHAT).
reactionColor.set(0xff00));
return response;
}
}
ServerAPI
There can be a lot of notifications so we are asking for the notifications since a given time
8. return null;
}
public static List<Notification> fetchNotifications(
long since, int amount) {
if(since < initTime - UIUtils.DAY) {
return null;
}
List<Notification> response = new ArrayList<>();
response.add(new Notification().id.set("Notify-1").
user.set(dummyUsers[0]).
text.set("liked Your Post").
date.set(initTime - 60000).
reaction.set("" + FontImage.MATERIAL_FAVORITE).
reactionColor.set(0xff0000));
response.add(new Notification().id.set("Notify-2").
user.set(dummyUsers[1]).
text.set("commented on your post").
date.set(initTime - 600000000).
reaction.set("" + FontImage.MATERIAL_CHAT).
reactionColor.set(0xff00));
return response;
}
}
ServerAPI
We construct hardcoded notifications for every entry with a specific hardcoded reaction
10. public class NotificationsContainer extends InfiniteContainer {
private long lastTime;
@Override
public Component[] fetchComponents(int index, int amount) {
if(index == 0) {
lastTime = System.currentTimeMillis();
}
List<Notification> response = ServerAPI.fetchNotifications(lastTime,
amount);
if(response == null) {
return null;
}
Component[] notifications = new Component[response.size()];
int iter = 0;
for(Notification n : response) {
lastTime = n.date.getLong();
notifications[iter] = createNotificationEntry(n);
iter++;
}
return notifications;
NotificationsContainer
Once we have all these changes in place the notification UI is pretty simple.
We use an InfiniteContainer just like we did in the newsfeed and use the timestamp of the last entry to fetch more
11. public class NotificationsContainer extends InfiniteContainer {
private long lastTime;
@Override
public Component[] fetchComponents(int index, int amount) {
if(index == 0) {
lastTime = System.currentTimeMillis();
}
List<Notification> response = ServerAPI.fetchNotifications(lastTime,
amount);
if(response == null) {
return null;
}
Component[] notifications = new Component[response.size()];
int iter = 0;
for(Notification n : response) {
lastTime = n.date.getLong();
notifications[iter] = createNotificationEntry(n);
iter++;
}
return notifications;
NotificationsContainer
We update the timestamp for the next request and create a notification component, the code is simple since there are no titles or special components
12. notifications[iter] = createNotificationEntry(n);
iter++;
}
return notifications;
}
private Container createNotificationEntry(Notification n) {
Image avatar = n.user.get().getAvatar(13);
Label icon = new Label("", "SmallBlueCircle");
icon.getAllStyles().setBorder(RoundBorder.create().
color(n.reactionColor.get()));
FontImage.setMaterialIcon(icon, n.reaction.get().charAt(0), 2);
Container avatarContainer = LayeredLayout.encloseIn(
new Label(avatar, "HalfPaddedContainer"),
FlowLayout.encloseRightBottom(icon));
RichTextView rt = new RichTextView("<b>" + n.user.get().fullName() +
"</b> " + n.text.get());
Label time = new Label(UIUtils.formatTimeAgo(n.date.get()),
"SmallBlueLabel");
Container yContainer = BoxLayout.encloseY(rt, time);
yContainer.setUIID("HalfPaddedContainer");
return BorderLayout.
centerEastWest(yContainer, null, avatarContainer);
}
}
NotificationsContainer
The reaction entry uses the blue circle UIID to annotate the reaction
13. notifications[iter] = createNotificationEntry(n);
iter++;
}
return notifications;
}
private Container createNotificationEntry(Notification n) {
Image avatar = n.user.get().getAvatar(13);
Label icon = new Label("", "SmallBlueCircle");
icon.getAllStyles().setBorder(RoundBorder.create().
color(n.reactionColor.get()));
FontImage.setMaterialIcon(icon, n.reaction.get().charAt(0), 2);
Container avatarContainer = LayeredLayout.encloseIn(
new Label(avatar, "HalfPaddedContainer"),
FlowLayout.encloseRightBottom(icon));
RichTextView rt = new RichTextView("<b>" + n.user.get().fullName() +
"</b> " + n.text.get());
Label time = new Label(UIUtils.formatTimeAgo(n.date.get()),
"SmallBlueLabel");
Container yContainer = BoxLayout.encloseY(rt, time);
yContainer.setUIID("HalfPaddedContainer");
return BorderLayout.
centerEastWest(yContainer, null, avatarContainer);
}
}
NotificationsContainer
Setting the color won't work correctly and might impact other borders. The solution is creating a new border instance
14. notifications[iter] = createNotificationEntry(n);
iter++;
}
return notifications;
}
private Container createNotificationEntry(Notification n) {
Image avatar = n.user.get().getAvatar(13);
Label icon = new Label("", "SmallBlueCircle");
icon.getAllStyles().setBorder(RoundBorder.create().
color(n.reactionColor.get()));
FontImage.setMaterialIcon(icon, n.reaction.get().charAt(0), 2);
Container avatarContainer = LayeredLayout.encloseIn(
new Label(avatar, "HalfPaddedContainer"),
FlowLayout.encloseRightBottom(icon));
RichTextView rt = new RichTextView("<b>" + n.user.get().fullName() +
"</b> " + n.text.get());
Label time = new Label(UIUtils.formatTimeAgo(n.date.get()),
"SmallBlueLabel");
Container yContainer = BoxLayout.encloseY(rt, time);
yContainer.setUIID("HalfPaddedContainer");
return BorderLayout.
centerEastWest(yContainer, null, avatarContainer);
}
}
NotificationsContainer
The avatar is placed in a layered layout and the reaction is placed above on the z-axis. We then use a flow layout to align the reaction to the bottom right
15. notifications[iter] = createNotificationEntry(n);
iter++;
}
return notifications;
}
private Container createNotificationEntry(Notification n) {
Image avatar = n.user.get().getAvatar(13);
Label icon = new Label("", "SmallBlueCircle");
icon.getAllStyles().setBorder(RoundBorder.create().
color(n.reactionColor.get()));
FontImage.setMaterialIcon(icon, n.reaction.get().charAt(0), 2);
Container avatarContainer = LayeredLayout.encloseIn(
new Label(avatar, "HalfPaddedContainer"),
FlowLayout.encloseRightBottom(icon));
RichTextView rt = new RichTextView("<b>" + n.user.get().fullName() +
"</b> " + n.text.get());
Label time = new Label(UIUtils.formatTimeAgo(n.date.get()),
"SmallBlueLabel");
Container yContainer = BoxLayout.encloseY(rt, time);
yContainer.setUIID("HalfPaddedContainer");
return BorderLayout.
centerEastWest(yContainer, null, avatarContainer);
}
}
NotificationsContainer
Just like the other forms we enclose the HTML label and the blue label below into a Y axis Container
16. SmallBlueLabel {
color: blue;
font-size: 2mm;
padding: 1mm;
font-family: "native:MainLight";
}
theme.css
There is just one missing piece in the CSS from the the listing. The code here is pretty self explanatory, it's a small blue label. With that we only have one last tab to
address...