SlideShare a Scribd company logo
Creating a Facebook Clone - Part XXX
How do we get friends?

We already implemented contact upload both in the server and the ServerAPI class. We need to map that to the UI though. Facebook nags about uploading contacts all
over the place which is probably something you should do too if you are building a social network. I don't want to get into the deep level of nagging so I decided to add a
FloatingActionButton to the friends container. This is a bit tricky though...
public class MainForm extends Form {
private Tabs mainUI = new Tabs();
public MainForm() {
super("", new BorderLayout());
mainUI.addTab("", MATERIAL_WEB, 5f, new NewsfeedContainer());
FloatingActionButton fab =
FloatingActionButton.createFAB(MATERIAL_IMPORT_CONTACTS);
Container friends = fab.bindFabToContainer(new FriendsContainer());
fab.addActionListener(e -> uploadContacts());
mainUI.addTab("", MATERIAL_PEOPLE_OUTLINE, 5f,
friends);
mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE,
5f, new NotificationsContainer());
mainUI.addTab("", MATERIAL_MENU, 5f,
new MoreContainer());
add(CENTER, mainUI);
getToolbar().addMaterialCommandToLeftBar("",
MATERIAL_CAMERA_ALT, 4, e -> {});
getToolbar().addMaterialCommandToRightBar("",
MainForm
Normally when we add a FloatingActionButton we add it to a Form. This hides some complexity, it seems like we are adding it to a Form but in fact it's added to a hidden
layered pane. When we want to add it only to a specific tab we need a better understanding of the underlying implementation.

The FloatingActionButton wraps the given Container in a layered layout and places itself on top. It then returns a new Container which you should use instead of the
original Container.

Normally that's pretty easy to do but it's obviously impossible to do within FriendsContainer as it's the Container... 

The solution is to implement this code in the MainForm class.

Here we create a fab for the contact upload
public class MainForm extends Form {
private Tabs mainUI = new Tabs();
public MainForm() {
super("", new BorderLayout());
mainUI.addTab("", MATERIAL_WEB, 5f, new NewsfeedContainer());
FloatingActionButton fab =
FloatingActionButton.createFAB(MATERIAL_IMPORT_CONTACTS);
Container friends = fab.bindFabToContainer(new FriendsContainer());
fab.addActionListener(e -> uploadContacts());
mainUI.addTab("", MATERIAL_PEOPLE_OUTLINE, 5f,
friends);
mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE,
5f, new NotificationsContainer());
mainUI.addTab("", MATERIAL_MENU, 5f,
new MoreContainer());
add(CENTER, mainUI);
getToolbar().addMaterialCommandToLeftBar("",
MATERIAL_CAMERA_ALT, 4, e -> {});
getToolbar().addMaterialCommandToRightBar("",
MainForm
We bind the fab and grab the return value instead of the FriendsContainer instance
public class MainForm extends Form {
private Tabs mainUI = new Tabs();
public MainForm() {
super("", new BorderLayout());
mainUI.addTab("", MATERIAL_WEB, 5f, new NewsfeedContainer());
FloatingActionButton fab =
FloatingActionButton.createFAB(MATERIAL_IMPORT_CONTACTS);
Container friends = fab.bindFabToContainer(new FriendsContainer());
fab.addActionListener(e -> uploadContacts());
mainUI.addTab("", MATERIAL_PEOPLE_OUTLINE, 5f,
friends);
mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE,
5f, new NotificationsContainer());
mainUI.addTab("", MATERIAL_MENU, 5f,
new MoreContainer());
add(CENTER, mainUI);
getToolbar().addMaterialCommandToLeftBar("",
MATERIAL_CAMERA_ALT, 4, e -> {});
getToolbar().addMaterialCommandToRightBar("",
MainForm
Pressing the fab will invoke the uploadContacts method
public class MainForm extends Form {
private Tabs mainUI = new Tabs();
public MainForm() {
super("", new BorderLayout());
mainUI.addTab("", MATERIAL_WEB, 5f, new NewsfeedContainer());
FloatingActionButton fab =
FloatingActionButton.createFAB(MATERIAL_IMPORT_CONTACTS);
Container friends = fab.bindFabToContainer(new FriendsContainer());
fab.addActionListener(e -> uploadContacts());
mainUI.addTab("", MATERIAL_PEOPLE_OUTLINE, 5f,
friends);
mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE,
5f, new NotificationsContainer());
mainUI.addTab("", MATERIAL_MENU, 5f,
new MoreContainer());
add(CENTER, mainUI);
getToolbar().addMaterialCommandToLeftBar("",
MATERIAL_CAMERA_ALT, 4, e -> {});
getToolbar().addMaterialCommandToRightBar("",
MainForm
Notice we add the returned friends Container that we wrapped with the fab
friends);
mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE,
5f, new NotificationsContainer());
mainUI.addTab("", MATERIAL_MENU, 5f,
new MoreContainer());
add(CENTER, mainUI);
getToolbar().addMaterialCommandToLeftBar("",
MATERIAL_CAMERA_ALT, 4, e -> {});
getToolbar().addMaterialCommandToRightBar("",
MATERIAL_CHAT, 4, e -> {});
Button searchButton = new Button("Search", "TitleSearch");
setMaterialIcon(searchButton, MATERIAL_SEARCH);
getToolbar().setTitleComponent(searchButton);
searchButton.addActionListener(e -> new SearchForm().show());
}
private void uploadContacts() {
startThread(() -> {
Contact[] cnt = Display.getInstance().
getAllContacts(true, true, false, true, true, false);
ServerAPI.uploadContacts(cnt);
}, "ContactUploader").start();
}
}
MainForm
Now that we have that we can implement the uploadContacts method
friends);
mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE,
5f, new NotificationsContainer());
mainUI.addTab("", MATERIAL_MENU, 5f,
new MoreContainer());
add(CENTER, mainUI);
getToolbar().addMaterialCommandToLeftBar("",
MATERIAL_CAMERA_ALT, 4, e -> {});
getToolbar().addMaterialCommandToRightBar("",
MATERIAL_CHAT, 4, e -> {});
Button searchButton = new Button("Search", "TitleSearch");
setMaterialIcon(searchButton, MATERIAL_SEARCH);
getToolbar().setTitleComponent(searchButton);
searchButton.addActionListener(e -> new SearchForm().show());
}
private void uploadContacts() {
startThread(() -> {
Contact[] cnt = Display.getInstance().
getAllContacts(true, true, false, true, true, false);
ServerAPI.uploadContacts(cnt);
}, "ContactUploader").start();
}
}
MainForm
We need to fetch contacts from the system, it makes sense to do this on a separate thread as this is a heavy task
friends);
mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE,
5f, new NotificationsContainer());
mainUI.addTab("", MATERIAL_MENU, 5f,
new MoreContainer());
add(CENTER, mainUI);
getToolbar().addMaterialCommandToLeftBar("",
MATERIAL_CAMERA_ALT, 4, e -> {});
getToolbar().addMaterialCommandToRightBar("",
MATERIAL_CHAT, 4, e -> {});
Button searchButton = new Button("Search", "TitleSearch");
setMaterialIcon(searchButton, MATERIAL_SEARCH);
getToolbar().setTitleComponent(searchButton);
searchButton.addActionListener(e -> new SearchForm().show());
}
private void uploadContacts() {
startThread(() -> {
Contact[] cnt = Display.getInstance().
getAllContacts(true, true, false, true, true, false);
ServerAPI.uploadContacts(cnt);
}, "ContactUploader").start();
}
}
MainForm
When we fetch the contacts we specify the fields we are interested in, if we specify fewer fields the fetch operation will be faster
friends);
mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE,
5f, new NotificationsContainer());
mainUI.addTab("", MATERIAL_MENU, 5f,
new MoreContainer());
add(CENTER, mainUI);
getToolbar().addMaterialCommandToLeftBar("",
MATERIAL_CAMERA_ALT, 4, e -> {});
getToolbar().addMaterialCommandToRightBar("",
MATERIAL_CHAT, 4, e -> {});
Button searchButton = new Button("Search", "TitleSearch");
setMaterialIcon(searchButton, MATERIAL_SEARCH);
getToolbar().setTitleComponent(searchButton);
searchButton.addActionListener(e -> new SearchForm().show());
}
private void uploadContacts() {
startThread(() -> {
Contact[] cnt = Display.getInstance().
getAllContacts(true, true, false, true, true, false);
ServerAPI.uploadContacts(cnt);
}, "ContactUploader").start();
}
}
MainForm
Despite the name of the startThread method we still need to invoke start on the returned thread…

Once this is done pressing that button should automatically prompt for permission and upload the contacts from your phone.
padding: 1.5mm;
text-align: center;
font-family: "native:MainRegular";
font-size: 3.5mm;
}
SmallLabel {
font-family: "native:MainLight";
font-size: 2mm;
}
FloatingActionButton {
background: #587EBE;
padding: 1.5mm;
text-align: center;
}
Comment {
color: black;
font-family: "native:MainThin";
font-size: 2.6mm;
border: cn1-pill-border;
background-color: #dbdbdb;
padding: 1mm 3mm 1mm 3mm;
margin: 1mm;
theme.css
In order to make the FAB look good we'll also need a couple of CSS changes. This mostly sets the FAB to Facebook style blue color.
add(UIUtils.createHalfSpace());
}
}
private Container friendRequestEntry(User u, Image avatar,
boolean request) {
Label name = new Label(u.fullName(), "FriendName");
Button confirm;
Button delete;
if(request) {
confirm = new Button("Confirm", "FriendConfirm");
delete = new Button("Delete", "FriendDelete");
bindConfirmDeleteEvent(u, confirm, delete);
} else {
confirm = new Button("Add Friend", "FriendConfirm");
delete = new Button("Remove", "FriendDelete");
bindAddRemoveFriendEvent(u, confirm, delete);
}
Container cnt =
BoxLayout.encloseY(name,
GridLayout.encloseIn(2, confirm, delete));
cnt.setUIID("PaddedContainer");
return BorderLayout.centerEastWest(cnt, null,
new Label(avatar, "Container"));
}
FriendsContainer
Each friend suggestion has a button to accept or remove the suggestion. Each friend request has similar button pairs.

We added the bindConfirmDeleteEvent or bindAddRemoveFriendEvent call to bind event listing to the buttons
} else {
confirm = new Button("Add Friend", "FriendConfirm");
delete = new Button("Remove", "FriendDelete");
bindAddRemoveFriendEvent(u, confirm, delete);
}
Container cnt =
BoxLayout.encloseY(name,
GridLayout.encloseIn(2, confirm, delete));
cnt.setUIID("PaddedContainer");
return BorderLayout.centerEastWest(cnt, null,
new Label(avatar, "Container"));
}
private Container findParent(Container button) {
if(button.getParent() != this) {
return findParent(button.getParent());
}
return button;
}
private void bindAddRemoveFriendEvent(
User u, Button add, Button remove) {
add.addActionListener(e -> {
ServerAPI.sendFriendRequest(u.id.get());
findParent(remove.getParent()).remove();
animateLayout(150);
FriendsContainer
When we remove or add a button we need to remove the Container of the friend from the hierarchy but at the stage where we bind the event we don't have an instance
of that Container yet. A workaround is to locate that Container by traversing through the hierarchy of the components. 

Here we recursively look through parent containers, we stop when the parent is the FriendsContainer itself. This relies on the fact that a friend suggestion is added
directly to the parent FriendsContainer.
if(button.getParent() != this) {
return findParent(button.getParent());
}
return button;
}
private void bindAddRemoveFriendEvent(
User u, Button add, Button remove) {
add.addActionListener(e -> {
ServerAPI.sendFriendRequest(u.id.get());
findParent(remove.getParent()).remove();
animateLayout(150);
ToastBar.showMessage("Sent friend request",
FontImage.MATERIAL_INFO);
ServerAPI.refreshMe();
});
remove.addActionListener(e -> {
findParent(remove.getParent()).remove();
animateLayout(150);
ServerAPI.me().peopleYouMayKnow.remove(u);
ServerAPI.update(ServerAPI.me());
});
}
private void bindConfirmDeleteEvent(
FriendsContainer
Once we have this implementing bindAddRemoveFriendEvent & bindConfirmDeleteEvent becomes trivial.
if(button.getParent() != this) {
return findParent(button.getParent());
}
return button;
}
private void bindAddRemoveFriendEvent(
User u, Button add, Button remove) {
add.addActionListener(e -> {
ServerAPI.sendFriendRequest(u.id.get());
findParent(remove.getParent()).remove();
animateLayout(150);
ToastBar.showMessage("Sent friend request",
FontImage.MATERIAL_INFO);
ServerAPI.refreshMe();
});
remove.addActionListener(e -> {
findParent(remove.getParent()).remove();
animateLayout(150);
ServerAPI.me().peopleYouMayKnow.remove(u);
ServerAPI.update(ServerAPI.me());
});
}
private void bindConfirmDeleteEvent(
FriendsContainer
We send a friend request to the server when the button is pressed
if(button.getParent() != this) {
return findParent(button.getParent());
}
return button;
}
private void bindAddRemoveFriendEvent(
User u, Button add, Button remove) {
add.addActionListener(e -> {
ServerAPI.sendFriendRequest(u.id.get());
findParent(remove.getParent()).remove();
animateLayout(150);
ToastBar.showMessage("Sent friend request",
FontImage.MATERIAL_INFO);
ServerAPI.refreshMe();
});
remove.addActionListener(e -> {
findParent(remove.getParent()).remove();
animateLayout(150);
ServerAPI.me().peopleYouMayKnow.remove(u);
ServerAPI.update(ServerAPI.me());
});
}
private void bindConfirmDeleteEvent(
FriendsContainer
After removing the Container with the friend suggestion we animate the remaining UI into place
if(button.getParent() != this) {
return findParent(button.getParent());
}
return button;
}
private void bindAddRemoveFriendEvent(
User u, Button add, Button remove) {
add.addActionListener(e -> {
ServerAPI.sendFriendRequest(u.id.get());
findParent(remove.getParent()).remove();
animateLayout(150);
ToastBar.showMessage("Sent friend request",
FontImage.MATERIAL_INFO);
ServerAPI.refreshMe();
});
remove.addActionListener(e -> {
findParent(remove.getParent()).remove();
animateLayout(150);
ServerAPI.me().peopleYouMayKnow.remove(u);
ServerAPI.update(ServerAPI.me());
});
}
private void bindConfirmDeleteEvent(
FriendsContainer
We refresh the me() object as it might be stale after this change
ServerAPI.me().peopleYouMayKnow.remove(u);
ServerAPI.update(ServerAPI.me());
});
}
private void bindConfirmDeleteEvent(
User u, Button add, Button remove) {
add.addActionListener(e -> {
findParent(remove.getParent()).remove();
animateLayout(150);
if(ServerAPI.acceptFriendRequest(u.id.get())) {
ServerAPI.refreshMe();
ToastBar.showMessage("You are now friends with " +
u.fullName(),
FontImage.MATERIAL_INFO);
}
});
remove.addActionListener(e -> {
findParent(remove.getParent()).remove();
animateLayout(150);
ServerAPI.me().friendRequests.remove(u);
ServerAPI.update(ServerAPI.me());
});
}
private Component createTitle(String title, int count) {
FriendsContainer
The rest of the code is nearly identical, we invoke the `ServerAPI` code to trigger the appropriate server changes. With this we can now send and accept friend requests.
public class NotificationsContainer extends InfiniteContainer {
@Override
public Component[] fetchComponents(int index, int amount) {
int page = index / amount;
if(index % amount > 0) {
page++;
}
List<Notification> response = ServerAPI.listNotifications(page,
amount);
if(response == null) {
return null;
}
Component[] notifications = new Component[response.size()];
int iter = 0;
for(Notification n : response) {
notifications[iter] = createNotificationEntry(n);
iter++;
}
return notifications;
}
private Container createNotificationEntry(Notification n) {
NotificationContainer
The last piece in the UI mapping is notification support. This is pretty trivial now that we finished everything else...
public class NotificationsContainer extends InfiniteContainer {
@Override
public Component[] fetchComponents(int index, int amount) {
int page = index / amount;
if(index % amount > 0) {
page++;
}
List<Notification> response = ServerAPI.listNotifications(page,
amount);
if(response == null) {
return null;
}
Component[] notifications = new Component[response.size()];
int iter = 0;
for(Notification n : response) {
notifications[iter] = createNotificationEntry(n);
iter++;
}
return notifications;
}
private Container createNotificationEntry(Notification n) {
NotificationContainer
Just like before we no longer need the lastTime value and can use the paging support instead
public class NotificationsContainer extends InfiniteContainer {
@Override
public Component[] fetchComponents(int index, int amount) {
int page = index / amount;
if(index % amount > 0) {
page++;
}
List<Notification> response = ServerAPI.listNotifications(page,
amount);
if(response == null) {
return null;
}
Component[] notifications = new Component[response.size()];
int iter = 0;
for(Notification n : response) {
notifications[iter] = createNotificationEntry(n);
iter++;
}
return notifications;
}
private Container createNotificationEntry(Notification n) {
NotificationContainer
We iterate over the notification objects and create entry components for each one. New notifications will arrive via push when we implement the push support later on.
With that we are effectively done with the first part of the app!

We have a fully working client/server mockup, there are still a lot of missing pieces but this just became a process of filling in the gaps. It's much easier to fill in the gaps
once the foundation is laid out in place.

More Related Content

PDF
Creating a Facebook Clone - Part XXX.pdf
PDF
Creating a Facebook Clone - Part XLIII.pdf
PDF
Creating a Facebook Clone - Part XLIII - Transcript.pdf
PDF
Creating a Facebook Clone - Part XIII - Transcript.pdf
PDF
Creating a Facebook Clone - Part XXVIII - Transcript.pdf
PDF
How do I - Create a List of Items - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part V - Transcript.pdf
PDF
Creating a Facebook Clone - Part XLVI.pdf
Creating a Facebook Clone - Part XXX.pdf
Creating a Facebook Clone - Part XLIII.pdf
Creating a Facebook Clone - Part XLIII - Transcript.pdf
Creating a Facebook Clone - Part XIII - Transcript.pdf
Creating a Facebook Clone - Part XXVIII - Transcript.pdf
How do I - Create a List of Items - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Facebook Clone - Part XLVI.pdf

Similar to Creating a Facebook Clone - Part XXX - Transcript.pdf (20)

PDF
Creating a Facebook Clone - Part XVI - Transcript.pdf
PDF
Creating a Facebook Clone - Part XXXVI - Transcript.pdf
PDF
Creating a Facebook Clone - Part XIII.pdf
PDF
Creating a Facebook Clone - Part XLVI - Transcript.pdf
PDF
Creating a Facebook Clone - Part XXIX - Transcript.pdf
PDF
Creating a Facebook Clone - Part IV - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part V.pdf
PDF
Creating a Facebook Clone - Part XXXVI.pdf
PDF
Creating a Facebook Clone - Part IV.pdf
PDF
Creating a Facebook Clone - Part IX.pdf
PDF
Creating a Facebook Clone - Part VI - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part X - Transcript.pdf
PDF
How do I - Create a List of Items.pdf
PDF
Creating a Whatsapp Clone - Part IX - Transcript.pdf
PDF
Creating a Facebook Clone - Part XXVIII.pdf
PDF
Creating an Uber Clone - Part XXXX - Transcript.pdf
PDF
Creating a Facebook Clone - Part III - Transcript.pdf
PDF
Android App Development 03 : Widget &amp; UI
PDF
Creating a Facebook Clone - Part VIII - Transcript.pdf
PDF
Android Best Practices
Creating a Facebook Clone - Part XVI - Transcript.pdf
Creating a Facebook Clone - Part XXXVI - Transcript.pdf
Creating a Facebook Clone - Part XIII.pdf
Creating a Facebook Clone - Part XLVI - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdf
Creating a Facebook Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part V.pdf
Creating a Facebook Clone - Part XXXVI.pdf
Creating a Facebook Clone - Part IV.pdf
Creating a Facebook Clone - Part IX.pdf
Creating a Facebook Clone - Part VI - Transcript.pdf
Creating a Whatsapp Clone - Part X - Transcript.pdf
How do I - Create a List of Items.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Facebook Clone - Part XXVIII.pdf
Creating an Uber Clone - Part XXXX - Transcript.pdf
Creating a Facebook Clone - Part III - Transcript.pdf
Android App Development 03 : Widget &amp; UI
Creating a Facebook Clone - Part VIII - Transcript.pdf
Android Best Practices
Ad

More from ShaiAlmog1 (20)

PDF
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
PDF
create-netflix-clone-06-client-ui.pdf
PDF
create-netflix-clone-01-introduction_transcript.pdf
PDF
create-netflix-clone-02-server_transcript.pdf
PDF
create-netflix-clone-04-server-continued_transcript.pdf
PDF
create-netflix-clone-01-introduction.pdf
PDF
create-netflix-clone-06-client-ui_transcript.pdf
PDF
create-netflix-clone-03-server.pdf
PDF
create-netflix-clone-04-server-continued.pdf
PDF
create-netflix-clone-05-client-model_transcript.pdf
PDF
create-netflix-clone-03-server_transcript.pdf
PDF
create-netflix-clone-02-server.pdf
PDF
create-netflix-clone-05-client-model.pdf
PDF
Creating a Whatsapp Clone - Part II.pdf
PDF
Creating a Whatsapp Clone - Part II - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV.pdf
PDF
Creating a Whatsapp Clone - Part I - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IX.pdf
PDF
Creating a Whatsapp Clone - Part VI.pdf
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
create-netflix-clone-06-client-ui.pdf
create-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-02-server_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-01-introduction.pdf
create-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-03-server.pdf
create-netflix-clone-04-server-continued.pdf
create-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-03-server_transcript.pdf
create-netflix-clone-02-server.pdf
create-netflix-clone-05-client-model.pdf
Creating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdf
Creating a Whatsapp Clone - Part IX.pdf
Creating a Whatsapp Clone - Part VI.pdf
Ad

Recently uploaded (20)

PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
Network Security Unit 5.pdf for BCA BBA.
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
KodekX | Application Modernization Development
PPTX
Cloud computing and distributed systems.
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
Approach and Philosophy of On baking technology
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
Spectroscopy.pptx food analysis technology
PDF
Empathic Computing: Creating Shared Understanding
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPTX
Big Data Technologies - Introduction.pptx
MIND Revenue Release Quarter 2 2025 Press Release
Network Security Unit 5.pdf for BCA BBA.
The AUB Centre for AI in Media Proposal.docx
The Rise and Fall of 3GPP – Time for a Sabbatical?
KodekX | Application Modernization Development
Cloud computing and distributed systems.
Building Integrated photovoltaic BIPV_UPV.pdf
Approach and Philosophy of On baking technology
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
“AI and Expert System Decision Support & Business Intelligence Systems”
Understanding_Digital_Forensics_Presentation.pptx
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
Reach Out and Touch Someone: Haptics and Empathic Computing
Spectroscopy.pptx food analysis technology
Empathic Computing: Creating Shared Understanding
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Mobile App Security Testing_ A Comprehensive Guide.pdf
Advanced methodologies resolving dimensionality complications for autism neur...
Big Data Technologies - Introduction.pptx

Creating a Facebook Clone - Part XXX - Transcript.pdf

  • 1. Creating a Facebook Clone - Part XXX How do we get friends? We already implemented contact upload both in the server and the ServerAPI class. We need to map that to the UI though. Facebook nags about uploading contacts all over the place which is probably something you should do too if you are building a social network. I don't want to get into the deep level of nagging so I decided to add a FloatingActionButton to the friends container. This is a bit tricky though...
  • 2. public class MainForm extends Form { private Tabs mainUI = new Tabs(); public MainForm() { super("", new BorderLayout()); mainUI.addTab("", MATERIAL_WEB, 5f, new NewsfeedContainer()); FloatingActionButton fab = FloatingActionButton.createFAB(MATERIAL_IMPORT_CONTACTS); Container friends = fab.bindFabToContainer(new FriendsContainer()); fab.addActionListener(e -> uploadContacts()); mainUI.addTab("", MATERIAL_PEOPLE_OUTLINE, 5f, friends); mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE, 5f, new NotificationsContainer()); mainUI.addTab("", MATERIAL_MENU, 5f, new MoreContainer()); add(CENTER, mainUI); getToolbar().addMaterialCommandToLeftBar("", MATERIAL_CAMERA_ALT, 4, e -> {}); getToolbar().addMaterialCommandToRightBar("", MainForm Normally when we add a FloatingActionButton we add it to a Form. This hides some complexity, it seems like we are adding it to a Form but in fact it's added to a hidden layered pane. When we want to add it only to a specific tab we need a better understanding of the underlying implementation. The FloatingActionButton wraps the given Container in a layered layout and places itself on top. It then returns a new Container which you should use instead of the original Container. Normally that's pretty easy to do but it's obviously impossible to do within FriendsContainer as it's the Container... The solution is to implement this code in the MainForm class. Here we create a fab for the contact upload
  • 3. public class MainForm extends Form { private Tabs mainUI = new Tabs(); public MainForm() { super("", new BorderLayout()); mainUI.addTab("", MATERIAL_WEB, 5f, new NewsfeedContainer()); FloatingActionButton fab = FloatingActionButton.createFAB(MATERIAL_IMPORT_CONTACTS); Container friends = fab.bindFabToContainer(new FriendsContainer()); fab.addActionListener(e -> uploadContacts()); mainUI.addTab("", MATERIAL_PEOPLE_OUTLINE, 5f, friends); mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE, 5f, new NotificationsContainer()); mainUI.addTab("", MATERIAL_MENU, 5f, new MoreContainer()); add(CENTER, mainUI); getToolbar().addMaterialCommandToLeftBar("", MATERIAL_CAMERA_ALT, 4, e -> {}); getToolbar().addMaterialCommandToRightBar("", MainForm We bind the fab and grab the return value instead of the FriendsContainer instance
  • 4. public class MainForm extends Form { private Tabs mainUI = new Tabs(); public MainForm() { super("", new BorderLayout()); mainUI.addTab("", MATERIAL_WEB, 5f, new NewsfeedContainer()); FloatingActionButton fab = FloatingActionButton.createFAB(MATERIAL_IMPORT_CONTACTS); Container friends = fab.bindFabToContainer(new FriendsContainer()); fab.addActionListener(e -> uploadContacts()); mainUI.addTab("", MATERIAL_PEOPLE_OUTLINE, 5f, friends); mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE, 5f, new NotificationsContainer()); mainUI.addTab("", MATERIAL_MENU, 5f, new MoreContainer()); add(CENTER, mainUI); getToolbar().addMaterialCommandToLeftBar("", MATERIAL_CAMERA_ALT, 4, e -> {}); getToolbar().addMaterialCommandToRightBar("", MainForm Pressing the fab will invoke the uploadContacts method
  • 5. public class MainForm extends Form { private Tabs mainUI = new Tabs(); public MainForm() { super("", new BorderLayout()); mainUI.addTab("", MATERIAL_WEB, 5f, new NewsfeedContainer()); FloatingActionButton fab = FloatingActionButton.createFAB(MATERIAL_IMPORT_CONTACTS); Container friends = fab.bindFabToContainer(new FriendsContainer()); fab.addActionListener(e -> uploadContacts()); mainUI.addTab("", MATERIAL_PEOPLE_OUTLINE, 5f, friends); mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE, 5f, new NotificationsContainer()); mainUI.addTab("", MATERIAL_MENU, 5f, new MoreContainer()); add(CENTER, mainUI); getToolbar().addMaterialCommandToLeftBar("", MATERIAL_CAMERA_ALT, 4, e -> {}); getToolbar().addMaterialCommandToRightBar("", MainForm Notice we add the returned friends Container that we wrapped with the fab
  • 6. friends); mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE, 5f, new NotificationsContainer()); mainUI.addTab("", MATERIAL_MENU, 5f, new MoreContainer()); add(CENTER, mainUI); getToolbar().addMaterialCommandToLeftBar("", MATERIAL_CAMERA_ALT, 4, e -> {}); getToolbar().addMaterialCommandToRightBar("", MATERIAL_CHAT, 4, e -> {}); Button searchButton = new Button("Search", "TitleSearch"); setMaterialIcon(searchButton, MATERIAL_SEARCH); getToolbar().setTitleComponent(searchButton); searchButton.addActionListener(e -> new SearchForm().show()); } private void uploadContacts() { startThread(() -> { Contact[] cnt = Display.getInstance(). getAllContacts(true, true, false, true, true, false); ServerAPI.uploadContacts(cnt); }, "ContactUploader").start(); } } MainForm Now that we have that we can implement the uploadContacts method
  • 7. friends); mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE, 5f, new NotificationsContainer()); mainUI.addTab("", MATERIAL_MENU, 5f, new MoreContainer()); add(CENTER, mainUI); getToolbar().addMaterialCommandToLeftBar("", MATERIAL_CAMERA_ALT, 4, e -> {}); getToolbar().addMaterialCommandToRightBar("", MATERIAL_CHAT, 4, e -> {}); Button searchButton = new Button("Search", "TitleSearch"); setMaterialIcon(searchButton, MATERIAL_SEARCH); getToolbar().setTitleComponent(searchButton); searchButton.addActionListener(e -> new SearchForm().show()); } private void uploadContacts() { startThread(() -> { Contact[] cnt = Display.getInstance(). getAllContacts(true, true, false, true, true, false); ServerAPI.uploadContacts(cnt); }, "ContactUploader").start(); } } MainForm We need to fetch contacts from the system, it makes sense to do this on a separate thread as this is a heavy task
  • 8. friends); mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE, 5f, new NotificationsContainer()); mainUI.addTab("", MATERIAL_MENU, 5f, new MoreContainer()); add(CENTER, mainUI); getToolbar().addMaterialCommandToLeftBar("", MATERIAL_CAMERA_ALT, 4, e -> {}); getToolbar().addMaterialCommandToRightBar("", MATERIAL_CHAT, 4, e -> {}); Button searchButton = new Button("Search", "TitleSearch"); setMaterialIcon(searchButton, MATERIAL_SEARCH); getToolbar().setTitleComponent(searchButton); searchButton.addActionListener(e -> new SearchForm().show()); } private void uploadContacts() { startThread(() -> { Contact[] cnt = Display.getInstance(). getAllContacts(true, true, false, true, true, false); ServerAPI.uploadContacts(cnt); }, "ContactUploader").start(); } } MainForm When we fetch the contacts we specify the fields we are interested in, if we specify fewer fields the fetch operation will be faster
  • 9. friends); mainUI.addTab("", MATERIAL_NOTIFICATIONS_NONE, 5f, new NotificationsContainer()); mainUI.addTab("", MATERIAL_MENU, 5f, new MoreContainer()); add(CENTER, mainUI); getToolbar().addMaterialCommandToLeftBar("", MATERIAL_CAMERA_ALT, 4, e -> {}); getToolbar().addMaterialCommandToRightBar("", MATERIAL_CHAT, 4, e -> {}); Button searchButton = new Button("Search", "TitleSearch"); setMaterialIcon(searchButton, MATERIAL_SEARCH); getToolbar().setTitleComponent(searchButton); searchButton.addActionListener(e -> new SearchForm().show()); } private void uploadContacts() { startThread(() -> { Contact[] cnt = Display.getInstance(). getAllContacts(true, true, false, true, true, false); ServerAPI.uploadContacts(cnt); }, "ContactUploader").start(); } } MainForm Despite the name of the startThread method we still need to invoke start on the returned thread… Once this is done pressing that button should automatically prompt for permission and upload the contacts from your phone.
  • 10. padding: 1.5mm; text-align: center; font-family: "native:MainRegular"; font-size: 3.5mm; } SmallLabel { font-family: "native:MainLight"; font-size: 2mm; } FloatingActionButton { background: #587EBE; padding: 1.5mm; text-align: center; } Comment { color: black; font-family: "native:MainThin"; font-size: 2.6mm; border: cn1-pill-border; background-color: #dbdbdb; padding: 1mm 3mm 1mm 3mm; margin: 1mm; theme.css In order to make the FAB look good we'll also need a couple of CSS changes. This mostly sets the FAB to Facebook style blue color.
  • 11. add(UIUtils.createHalfSpace()); } } private Container friendRequestEntry(User u, Image avatar, boolean request) { Label name = new Label(u.fullName(), "FriendName"); Button confirm; Button delete; if(request) { confirm = new Button("Confirm", "FriendConfirm"); delete = new Button("Delete", "FriendDelete"); bindConfirmDeleteEvent(u, confirm, delete); } else { confirm = new Button("Add Friend", "FriendConfirm"); delete = new Button("Remove", "FriendDelete"); bindAddRemoveFriendEvent(u, confirm, delete); } Container cnt = BoxLayout.encloseY(name, GridLayout.encloseIn(2, confirm, delete)); cnt.setUIID("PaddedContainer"); return BorderLayout.centerEastWest(cnt, null, new Label(avatar, "Container")); } FriendsContainer Each friend suggestion has a button to accept or remove the suggestion. Each friend request has similar button pairs. We added the bindConfirmDeleteEvent or bindAddRemoveFriendEvent call to bind event listing to the buttons
  • 12. } else { confirm = new Button("Add Friend", "FriendConfirm"); delete = new Button("Remove", "FriendDelete"); bindAddRemoveFriendEvent(u, confirm, delete); } Container cnt = BoxLayout.encloseY(name, GridLayout.encloseIn(2, confirm, delete)); cnt.setUIID("PaddedContainer"); return BorderLayout.centerEastWest(cnt, null, new Label(avatar, "Container")); } private Container findParent(Container button) { if(button.getParent() != this) { return findParent(button.getParent()); } return button; } private void bindAddRemoveFriendEvent( User u, Button add, Button remove) { add.addActionListener(e -> { ServerAPI.sendFriendRequest(u.id.get()); findParent(remove.getParent()).remove(); animateLayout(150); FriendsContainer When we remove or add a button we need to remove the Container of the friend from the hierarchy but at the stage where we bind the event we don't have an instance of that Container yet. A workaround is to locate that Container by traversing through the hierarchy of the components. Here we recursively look through parent containers, we stop when the parent is the FriendsContainer itself. This relies on the fact that a friend suggestion is added directly to the parent FriendsContainer.
  • 13. if(button.getParent() != this) { return findParent(button.getParent()); } return button; } private void bindAddRemoveFriendEvent( User u, Button add, Button remove) { add.addActionListener(e -> { ServerAPI.sendFriendRequest(u.id.get()); findParent(remove.getParent()).remove(); animateLayout(150); ToastBar.showMessage("Sent friend request", FontImage.MATERIAL_INFO); ServerAPI.refreshMe(); }); remove.addActionListener(e -> { findParent(remove.getParent()).remove(); animateLayout(150); ServerAPI.me().peopleYouMayKnow.remove(u); ServerAPI.update(ServerAPI.me()); }); } private void bindConfirmDeleteEvent( FriendsContainer Once we have this implementing bindAddRemoveFriendEvent & bindConfirmDeleteEvent becomes trivial.
  • 14. if(button.getParent() != this) { return findParent(button.getParent()); } return button; } private void bindAddRemoveFriendEvent( User u, Button add, Button remove) { add.addActionListener(e -> { ServerAPI.sendFriendRequest(u.id.get()); findParent(remove.getParent()).remove(); animateLayout(150); ToastBar.showMessage("Sent friend request", FontImage.MATERIAL_INFO); ServerAPI.refreshMe(); }); remove.addActionListener(e -> { findParent(remove.getParent()).remove(); animateLayout(150); ServerAPI.me().peopleYouMayKnow.remove(u); ServerAPI.update(ServerAPI.me()); }); } private void bindConfirmDeleteEvent( FriendsContainer We send a friend request to the server when the button is pressed
  • 15. if(button.getParent() != this) { return findParent(button.getParent()); } return button; } private void bindAddRemoveFriendEvent( User u, Button add, Button remove) { add.addActionListener(e -> { ServerAPI.sendFriendRequest(u.id.get()); findParent(remove.getParent()).remove(); animateLayout(150); ToastBar.showMessage("Sent friend request", FontImage.MATERIAL_INFO); ServerAPI.refreshMe(); }); remove.addActionListener(e -> { findParent(remove.getParent()).remove(); animateLayout(150); ServerAPI.me().peopleYouMayKnow.remove(u); ServerAPI.update(ServerAPI.me()); }); } private void bindConfirmDeleteEvent( FriendsContainer After removing the Container with the friend suggestion we animate the remaining UI into place
  • 16. if(button.getParent() != this) { return findParent(button.getParent()); } return button; } private void bindAddRemoveFriendEvent( User u, Button add, Button remove) { add.addActionListener(e -> { ServerAPI.sendFriendRequest(u.id.get()); findParent(remove.getParent()).remove(); animateLayout(150); ToastBar.showMessage("Sent friend request", FontImage.MATERIAL_INFO); ServerAPI.refreshMe(); }); remove.addActionListener(e -> { findParent(remove.getParent()).remove(); animateLayout(150); ServerAPI.me().peopleYouMayKnow.remove(u); ServerAPI.update(ServerAPI.me()); }); } private void bindConfirmDeleteEvent( FriendsContainer We refresh the me() object as it might be stale after this change
  • 17. ServerAPI.me().peopleYouMayKnow.remove(u); ServerAPI.update(ServerAPI.me()); }); } private void bindConfirmDeleteEvent( User u, Button add, Button remove) { add.addActionListener(e -> { findParent(remove.getParent()).remove(); animateLayout(150); if(ServerAPI.acceptFriendRequest(u.id.get())) { ServerAPI.refreshMe(); ToastBar.showMessage("You are now friends with " + u.fullName(), FontImage.MATERIAL_INFO); } }); remove.addActionListener(e -> { findParent(remove.getParent()).remove(); animateLayout(150); ServerAPI.me().friendRequests.remove(u); ServerAPI.update(ServerAPI.me()); }); } private Component createTitle(String title, int count) { FriendsContainer The rest of the code is nearly identical, we invoke the `ServerAPI` code to trigger the appropriate server changes. With this we can now send and accept friend requests.
  • 18. public class NotificationsContainer extends InfiniteContainer { @Override public Component[] fetchComponents(int index, int amount) { int page = index / amount; if(index % amount > 0) { page++; } List<Notification> response = ServerAPI.listNotifications(page, amount); if(response == null) { return null; } Component[] notifications = new Component[response.size()]; int iter = 0; for(Notification n : response) { notifications[iter] = createNotificationEntry(n); iter++; } return notifications; } private Container createNotificationEntry(Notification n) { NotificationContainer The last piece in the UI mapping is notification support. This is pretty trivial now that we finished everything else...
  • 19. public class NotificationsContainer extends InfiniteContainer { @Override public Component[] fetchComponents(int index, int amount) { int page = index / amount; if(index % amount > 0) { page++; } List<Notification> response = ServerAPI.listNotifications(page, amount); if(response == null) { return null; } Component[] notifications = new Component[response.size()]; int iter = 0; for(Notification n : response) { notifications[iter] = createNotificationEntry(n); iter++; } return notifications; } private Container createNotificationEntry(Notification n) { NotificationContainer Just like before we no longer need the lastTime value and can use the paging support instead
  • 20. public class NotificationsContainer extends InfiniteContainer { @Override public Component[] fetchComponents(int index, int amount) { int page = index / amount; if(index % amount > 0) { page++; } List<Notification> response = ServerAPI.listNotifications(page, amount); if(response == null) { return null; } Component[] notifications = new Component[response.size()]; int iter = 0; for(Notification n : response) { notifications[iter] = createNotificationEntry(n); iter++; } return notifications; } private Container createNotificationEntry(Notification n) { NotificationContainer We iterate over the notification objects and create entry components for each one. New notifications will arrive via push when we implement the push support later on. With that we are effectively done with the first part of the app! We have a fully working client/server mockup, there are still a lot of missing pieces but this just became a process of filling in the gaps. It's much easier to fill in the gaps once the foundation is laid out in place.