SlideShare a Scribd company logo
Finishing the App - Part I
In this section we’ll finish the restaurant app while skipping some big pieces such as the implementation of the server
The Server
✦The server code is attached to this course but I’m not
discussing it
✦I want to avoid that complexity in this course and I’m
keeping it for the app builder course
© Codename One 2017 all rights reserved
I’m attaching the server project to the module so you would be able to reference and probably run it but the goal of this course is to teach Codename One and not to
teach server development.

In the next course we’ll cover sever development more in depth and touch on some of the work we did for this server.
Finishing the App
✦Finishing an app is the hardest thing for most
developers
✦We procrastinate and find other things that need
doing and can’t make it “past the finish line”
✦The trick is to decide on a very restricted list of
features and work thru them until the beta
✦Don’t add to that list!

It needs to be complete
© Codename One 2017 all rights reserved
The goal of this module is to finish the app. It’s not a goal to create a perfect app but rather to reach a milestone that we can use.

One of the hardest things for developers to do is “finish”. We constantly find something new that needs doing and it becomes harder to reach a point of release.

That’s why it’s crucial to define strict limits and curb your ambition so you can get something into the hands of users.

Restricting the feature set to a strict list only works if you maintain discipline and don’t embellish the list. Don’t add to the entries and don’t add new entries.
List of things to fix
✦Bind data to the server
✦Add support for delivery address collection
✦Add some validation such as minimum order
✦Fix styling of components for pressed states
✦Implement search in the client
✦Implement selection based on dish category
✦Store dish image data in the server/database and fetch/
cache dynamically
© Codename One 2017 all rights reserved
This is the current list I came up with for this app, it includes many missing pieces and some of them are pretty big but generally it represents the things we need to add
or fix. I intend to implement all of these within this module which is relatively quick. Normally a list like this should take longer to implement.
public class DishService {
public static boolean hasCachedDishList() {
return Storage.getInstance().exists("DishListCache");
}
public static void loadDishesFromCache() {
JSONParser p = new JSONParser();
try {
Map<String, Object> m = p.parseJSON(new InputStreamReader(
Storage.getInstance().createInputStream("DishListCache"), "UTF-8"));
List<Object> l = (List<Object>)m.get("dishes");
Restaurant.getInstance().menu.get().dishes.clear();
for(Object o : l) {
Dish d = new Dish();
d.getPropertyIndex().populateFromMap((Map)o);
Restaurant.getInstance().menu.get().dishes.add(d);
}
List<String> cats = (List<String>)m.get("categories");
Restaurant.getInstance().menu.get().categories.clear();
for(String str : cats) {
Restaurant.getInstance().menu.get().categories.add(str);
}
if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) {
Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE);
}
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading list of dishes: " + err);
Log.sendLog();
DishService (Client)
Lets get started with the first item on the agenda the network layer and I’ll do this by creating an abstraction of the server communication. Despite skipping the server
code most of this code should be pretty clear as we go over it.
public class DishService {
public static boolean hasCachedDishList() {
return Storage.getInstance().exists("DishListCache");
}
public static void loadDishesFromCache() {
JSONParser p = new JSONParser();
try {
Map<String, Object> m = p.parseJSON(new InputStreamReader(
Storage.getInstance().createInputStream("DishListCache"), "UTF-8"));
List<Object> l = (List<Object>)m.get("dishes");
Restaurant.getInstance().menu.get().dishes.clear();
for(Object o : l) {
Dish d = new Dish();
d.getPropertyIndex().populateFromMap((Map)o);
Restaurant.getInstance().menu.get().dishes.add(d);
}
List<String> cats = (List<String>)m.get("categories");
Restaurant.getInstance().menu.get().categories.clear();
for(String str : cats) {
Restaurant.getInstance().menu.get().categories.add(str);
}
if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) {
Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE);
}
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading list of dishes: " + err);
Log.sendLog();
DishService (Client)
The dish service implements caching for the dish list we fetch from the server to make sure the app loads instantly. If we fetch new data we will show it later as it’s more
important that the app loads quickly. Here we check whether cache even exists, this is important for first time activation.
public class DishService {
public static boolean hasCachedDishList() {
return Storage.getInstance().exists("DishListCache");
}
public static void loadDishesFromCache() {
JSONParser p = new JSONParser();
try {
Map<String, Object> m = p.parseJSON(new InputStreamReader(
Storage.getInstance().createInputStream("DishListCache"), "UTF-8"));
List<Object> l = (List<Object>)m.get("dishes");
Restaurant.getInstance().menu.get().dishes.clear();
for(Object o : l) {
Dish d = new Dish();
d.getPropertyIndex().populateFromMap((Map)o);
Restaurant.getInstance().menu.get().dishes.add(d);
}
List<String> cats = (List<String>)m.get("categories");
Restaurant.getInstance().menu.get().categories.clear();
for(String str : cats) {
Restaurant.getInstance().menu.get().categories.add(str);
}
if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) {
Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE);
}
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading list of dishes: " + err);
Log.sendLog();
DishService (Client)
Assuming cache exists we can just load the data from the cache into the global restaurant object. The cache has the JSON as it was returned from the server so we can
parse it using the standard parsing code.

This is convenient for debugging as we can inspect the json file in the filesystem for the simulator and see what’s saved locally
public class DishService {
public static boolean hasCachedDishList() {
return Storage.getInstance().exists("DishListCache");
}
public static void loadDishesFromCache() {
JSONParser p = new JSONParser();
try {
Map<String, Object> m = p.parseJSON(new InputStreamReader(
Storage.getInstance().createInputStream("DishListCache"), "UTF-8"));
List<Object> l = (List<Object>)m.get("dishes");
Restaurant.getInstance().menu.get().dishes.clear();
for(Object o : l) {
Dish d = new Dish();
d.getPropertyIndex().populateFromMap((Map)o);
Restaurant.getInstance().menu.get().dishes.add(d);
}
List<String> cats = (List<String>)m.get("categories");
Restaurant.getInstance().menu.get().categories.clear();
for(String str : cats) {
Restaurant.getInstance().menu.get().categories.add(str);
}
if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) {
Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE);
}
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading list of dishes: " + err);
Log.sendLog();
DishService (Client)
The JSON file contains a map object which contains two arrays dishes and categories. The dishes array contains all the information in a dish and every entry within this
map has the same key value pairs as the Dish object properties. One of the features of property objects is the ability to populate a property from a Map object or from
JSON directly and this is exactly the case for which this is useful
public class DishService {
public static boolean hasCachedDishList() {
return Storage.getInstance().exists("DishListCache");
}
public static void loadDishesFromCache() {
JSONParser p = new JSONParser();
try {
Map<String, Object> m = p.parseJSON(new InputStreamReader(
Storage.getInstance().createInputStream("DishListCache"), "UTF-8"));
List<Object> l = (List<Object>)m.get("dishes");
Restaurant.getInstance().menu.get().dishes.clear();
for(Object o : l) {
Dish d = new Dish();
d.getPropertyIndex().populateFromMap((Map)o);
Restaurant.getInstance().menu.get().dishes.add(d);
}
List<String> cats = (List<String>)m.get("categories");
Restaurant.getInstance().menu.get().categories.clear();
for(String str : cats) {
Restaurant.getInstance().menu.get().categories.add(str);
}
if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) {
Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE);
}
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading list of dishes: " + err);
Log.sendLog();
DishService (Client)
The categories are just string names of all the available categories. We could probably just go over the dishes and extract the categories from there though.

Once we add the categories we set the boolean flag indicating the download was finished, this is important as external code might have a listener bound to the property
so it can refresh the UI once the data finished loading.
public static void updateDishesFromServerSync() {
NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer());
}
public static void updateDishesFromServerAsync() {
NetworkManager.getInstance().addToQueue(updateDishesFromServer());
}
private static ConnectionRequest updateDishesFromServer() {
ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) {
private boolean autoLoad;
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8"));
String stamp = (String)m.get("stamp");
if(stamp != null) {
Preferences.set("lastTimeStamp", stamp);
OutputStream o = Storage.getInstance().createOutputStream("DishListCache");
o.write(Result.fromContent(m).toString().getBytes());
o.close();
autoLoad = true;
}
}
protected void postResponse() {
if(autoLoad) {
loadDishesFromCache();
}
}
};
cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0"));
cr.addArgument("appId", MyRestaurant.APP_AUTH);
return cr;
DishService (Client)
Moving on we have two separate methods to update the dishes. They are both identical and just use the addToQueueAndWait or addToQueue. 

The reason we need both is simple, we use addToQueue when the app launches as we want to launch right away and don’t want to wait for the data. However, pull to
refresh needs to block until the data is here and in that case we need to use addToQueueAndWait.
public static void updateDishesFromServerSync() {
NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer());
}
public static void updateDishesFromServerAsync() {
NetworkManager.getInstance().addToQueue(updateDishesFromServer());
}
private static ConnectionRequest updateDishesFromServer() {
ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) {
private boolean autoLoad;
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8"));
String stamp = (String)m.get("stamp");
if(stamp != null) {
Preferences.set("lastTimeStamp", stamp);
OutputStream o = Storage.getInstance().createOutputStream("DishListCache");
o.write(Result.fromContent(m).toString().getBytes());
o.close();
autoLoad = true;
}
}
protected void postResponse() {
if(autoLoad) {
loadDishesFromCache();
}
}
};
cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0"));
cr.addArgument("appId", MyRestaurant.APP_AUTH);
return cr;
DishService (Client)
The aptly named update dishes from server is the common code for both update methods, it creates a connection request to the dish URL that performs a GET HTTP
method on the server.
public static void updateDishesFromServerSync() {
NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer());
}
public static void updateDishesFromServerAsync() {
NetworkManager.getInstance().addToQueue(updateDishesFromServer());
}
private static ConnectionRequest updateDishesFromServer() {
ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) {
private boolean autoLoad;
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8"));
String stamp = (String)m.get("stamp");
if(stamp != null) {
Preferences.set("lastTimeStamp", stamp);
OutputStream o = Storage.getInstance().createOutputStream("DishListCache");
o.write(Result.fromContent(m).toString().getBytes());
o.close();
autoLoad = true;
}
}
protected void postResponse() {
if(autoLoad) {
loadDishesFromCache();
}
}
};
cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0"));
cr.addArgument("appId", MyRestaurant.APP_AUTH);
return cr;
DishService (Client)
The response is parsed mostly so we can check the time stamp and update the local time stamp. The time stamp trick allows us to send a request with the last received
time stamp and initially we just send 0. This means the server is responsible for updating that value and if no change was made since the last time the server changed
that value we don’t need to receive new data.
public static void updateDishesFromServerSync() {
NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer());
}
public static void updateDishesFromServerAsync() {
NetworkManager.getInstance().addToQueue(updateDishesFromServer());
}
private static ConnectionRequest updateDishesFromServer() {
ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) {
private boolean autoLoad;
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8"));
String stamp = (String)m.get("stamp");
if(stamp != null) {
Preferences.set("lastTimeStamp", stamp);
OutputStream o = Storage.getInstance().createOutputStream("DishListCache");
o.write(Result.fromContent(m).toString().getBytes());
o.close();
autoLoad = true;
}
}
protected void postResponse() {
if(autoLoad) {
loadDishesFromCache();
}
}
};
cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0"));
cr.addArgument("appId", MyRestaurant.APP_AUTH);
return cr;
DishService (Client)
You will notice that if the data is changed we save it to cache then load it from cache later. This is pretty important, we call the loading code from the post response call
which runs on the event dispatch thread. The other callbacks in the connection request occur on a network thread but the post response call is on the event dispatch
thread and thus it can change the UI or UI related elements.
public static void updateDishesFromServerSync() {
NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer());
}
public static void updateDishesFromServerAsync() {
NetworkManager.getInstance().addToQueue(updateDishesFromServer());
}
private static ConnectionRequest updateDishesFromServer() {
ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) {
private boolean autoLoad;
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8"));
String stamp = (String)m.get("stamp");
if(stamp != null) {
Preferences.set("lastTimeStamp", stamp);
OutputStream o = Storage.getInstance().createOutputStream("DishListCache");
o.write(Result.fromContent(m).toString().getBytes());
o.close();
autoLoad = true;
}
}
protected void postResponse() {
if(autoLoad) {
loadDishesFromCache();
}
}
};
cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0"));
cr.addArgument("appId", MyRestaurant.APP_AUTH);
return cr;
DishService (Client)
As I mentioned before we send the time stamp and also an authorization key to the server. The latter is hardcoded forthe app but the former gets updated by the server
every time the data changes.
@Override
protected Container createContent() {
Container c = new Container(BoxLayout.y());
final Menu m = Restaurant.getInstance().menu.get();
if(m.dishes.size() == 0) {
if(DishService.hasCachedDishList()) {
DishService.loadDishesFromCache();
}
}
if(m.dishes.size() > 0) {
for(Dish currentDish : m.dishes) {
c.add(createDishContainer(currentDish));
}
c.addPullToRefresh(() -> {
DishService.updateDishesFromServerSync();
c.removeAll();
for(Dish currentDish : m.dishes) {
c.add(createDishContainer(currentDish));
}
c.revalidate();
});
} else {
MainMenuForm
Let’s move on to the main menu form where we now fetch the dishes from cache if they aren’t loaded yet.
@Override
protected Container createContent() {
Container c = new Container(BoxLayout.y());
final Menu m = Restaurant.getInstance().menu.get();
if(m.dishes.size() == 0) {
if(DishService.hasCachedDishList()) {
DishService.loadDishesFromCache();
}
}
if(m.dishes.size() > 0) {
for(Dish currentDish : m.dishes) {
c.add(createDishContainer(currentDish));
}
c.addPullToRefresh(() -> {
DishService.updateDishesFromServerSync();
c.removeAll();
for(Dish currentDish : m.dishes) {
c.add(createDishContainer(currentDish));
}
c.revalidate();
});
} else {
MainMenuForm
You will also notice that the pull to refresh call makes use of the update sync method to fetch the updated dishes synchronously.
} else {
final Container infi = FlowLayout.encloseCenter(new InfiniteProgress());
c.add(infi);
m.dishDownloadFinished.addChangeListener(p -> {
c.removeAll();
for(Dish currentDish : m.dishes) {
c.add(createDishContainer(currentDish));
}
categoryModel.removeAll();
for(String s : m.categories) {
categoryModel.addItem(s);
}
c.revalidate();
});
}
c.setScrollableY(true);
c.setScrollVisible(false);
dishesContainer = c;
return c;
}
MainMenuForm
This block represents a very special case of the first invocation when we have no dishes available and are still waiting. In this case we place an infinite progress in place
and bind a listener to the finished download so we can replace the progress. Notice we aren’t concerned about dishes suddenly appearing in a background thread
because everything is modified via the event dispatch thread so we can rely on this being in sync.

More Related Content

Similar to Finishing the App - Part 1 - Transcript.pdf

Murach : How to work with session state and cookies
Murach : How to work with session state and cookiesMurach : How to work with session state and cookies
Murach : How to work with session state and cookiesMahmoudOHassouna
 
SQLite and ORM Binding - Part 2.pdf
SQLite and ORM Binding - Part 2.pdfSQLite and ORM Binding - Part 2.pdf
SQLite and ORM Binding - Part 2.pdfShaiAlmog1
 
Creating a Facebook Clone - Part XXIX - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdfCreating a Facebook Clone - Part XXIX - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdfShaiAlmog1
 
Angular.js Primer in Aalto University
Angular.js Primer in Aalto UniversityAngular.js Primer in Aalto University
Angular.js Primer in Aalto UniversitySC5.io
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Robert DeLuca
 
SharePoint Conference 2018 - APIs, APIs everywhere!
SharePoint Conference 2018 - APIs, APIs everywhere!SharePoint Conference 2018 - APIs, APIs everywhere!
SharePoint Conference 2018 - APIs, APIs everywhere!Sébastien Levert
 
Android dev toolbox
Android dev toolboxAndroid dev toolbox
Android dev toolboxShem Magnezi
 
Restaurant Server - Transcript.pdf
Restaurant Server - Transcript.pdfRestaurant Server - Transcript.pdf
Restaurant Server - Transcript.pdfShaiAlmog1
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on AndroidSven Haiges
 
Android tutorials8 todo_list
Android tutorials8 todo_listAndroid tutorials8 todo_list
Android tutorials8 todo_listVlad Kolesnyk
 
React table tutorial project setup, use table, and usefilter
React table tutorial project setup, use table, and usefilterReact table tutorial project setup, use table, and usefilter
React table tutorial project setup, use table, and usefilterKaty Slemon
 
Server Side Swift - AppBuilders 2017
Server Side Swift - AppBuilders 2017Server Side Swift - AppBuilders 2017
Server Side Swift - AppBuilders 2017Jens Ravens
 
Adding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsAdding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsJeff Durta
 
Day 8: Dealing with Lists and ListViews
Day 8: Dealing with Lists and ListViewsDay 8: Dealing with Lists and ListViews
Day 8: Dealing with Lists and ListViewsAhsanul Karim
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Elena Kolevska
 
Android tutorials8 todo_list
Android tutorials8 todo_listAndroid tutorials8 todo_list
Android tutorials8 todo_listVlad Kolesnyk
 
How to Use ReorderableListView Widget in Flutter App Development.pptx
How to Use ReorderableListView Widget in Flutter App Development.pptxHow to Use ReorderableListView Widget in Flutter App Development.pptx
How to Use ReorderableListView Widget in Flutter App Development.pptxFlutter Agency
 

Similar to Finishing the App - Part 1 - Transcript.pdf (20)

Murach : How to work with session state and cookies
Murach : How to work with session state and cookiesMurach : How to work with session state and cookies
Murach : How to work with session state and cookies
 
SQLite and ORM Binding - Part 2.pdf
SQLite and ORM Binding - Part 2.pdfSQLite and ORM Binding - Part 2.pdf
SQLite and ORM Binding - Part 2.pdf
 
Creating a Facebook Clone - Part XXIX - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdfCreating a Facebook Clone - Part XXIX - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdf
 
Angular.js Primer in Aalto University
Angular.js Primer in Aalto UniversityAngular.js Primer in Aalto University
Angular.js Primer in Aalto University
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
 
SharePoint Conference 2018 - APIs, APIs everywhere!
SharePoint Conference 2018 - APIs, APIs everywhere!SharePoint Conference 2018 - APIs, APIs everywhere!
SharePoint Conference 2018 - APIs, APIs everywhere!
 
Android dev toolbox
Android dev toolboxAndroid dev toolbox
Android dev toolbox
 
Restaurant Server - Transcript.pdf
Restaurant Server - Transcript.pdfRestaurant Server - Transcript.pdf
Restaurant Server - Transcript.pdf
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
 
Android tutorials8 todo_list
Android tutorials8 todo_listAndroid tutorials8 todo_list
Android tutorials8 todo_list
 
React table tutorial project setup, use table, and usefilter
React table tutorial project setup, use table, and usefilterReact table tutorial project setup, use table, and usefilter
React table tutorial project setup, use table, and usefilter
 
Firebase ng2 zurich
Firebase ng2 zurichFirebase ng2 zurich
Firebase ng2 zurich
 
Android Sample Project By Wael Almadhoun
Android Sample Project By Wael AlmadhounAndroid Sample Project By Wael Almadhoun
Android Sample Project By Wael Almadhoun
 
Server Side Swift - AppBuilders 2017
Server Side Swift - AppBuilders 2017Server Side Swift - AppBuilders 2017
Server Side Swift - AppBuilders 2017
 
Adding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsAdding a modern twist to legacy web applications
Adding a modern twist to legacy web applications
 
Day 8: Dealing with Lists and ListViews
Day 8: Dealing with Lists and ListViewsDay 8: Dealing with Lists and ListViews
Day 8: Dealing with Lists and ListViews
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5
 
Android tutorials8 todo_list
Android tutorials8 todo_listAndroid tutorials8 todo_list
Android tutorials8 todo_list
 
How to Use ReorderableListView Widget in Flutter App Development.pptx
How to Use ReorderableListView Widget in Flutter App Development.pptxHow to Use ReorderableListView Widget in Flutter App Development.pptx
How to Use ReorderableListView Widget in Flutter App Development.pptx
 
Android best practices
Android best practicesAndroid best practices
Android best practices
 

More from ShaiAlmog1

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

More from ShaiAlmog1 (20)

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

Recently uploaded

Syngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdfSyngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdfSyngulon
 
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdfHow Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdfFIDO Alliance
 
Intro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджераIntro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджераMark Opanasiuk
 
Agentic RAG What it is its types applications and implementation.pdf
Agentic RAG What it is its types applications and implementation.pdfAgentic RAG What it is its types applications and implementation.pdf
Agentic RAG What it is its types applications and implementation.pdfChristopherTHyatt
 
Powerful Start- the Key to Project Success, Barbara Laskowska
Powerful Start- the Key to Project Success, Barbara LaskowskaPowerful Start- the Key to Project Success, Barbara Laskowska
Powerful Start- the Key to Project Success, Barbara LaskowskaCzechDreamin
 
The Metaverse: Are We There Yet?
The  Metaverse:    Are   We  There  Yet?The  Metaverse:    Are   We  There  Yet?
The Metaverse: Are We There Yet?Mark Billinghurst
 
Optimizing NoSQL Performance Through Observability
Optimizing NoSQL Performance Through ObservabilityOptimizing NoSQL Performance Through Observability
Optimizing NoSQL Performance Through ObservabilityScyllaDB
 
IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024IoTAnalytics
 
Salesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
Salesforce Adoption – Metrics, Methods, and Motivation, Antone KomSalesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
Salesforce Adoption – Metrics, Methods, and Motivation, Antone KomCzechDreamin
 
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...FIDO Alliance
 
Designing for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at ComcastDesigning for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at ComcastUXDXConf
 
UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2DianaGray10
 
Free and Effective: Making Flows Publicly Accessible, Yumi Ibrahimzade
Free and Effective: Making Flows Publicly Accessible, Yumi IbrahimzadeFree and Effective: Making Flows Publicly Accessible, Yumi Ibrahimzade
Free and Effective: Making Flows Publicly Accessible, Yumi IbrahimzadeCzechDreamin
 
10 Differences between Sales Cloud and CPQ, Blanka Doktorová
10 Differences between Sales Cloud and CPQ, Blanka Doktorová10 Differences between Sales Cloud and CPQ, Blanka Doktorová
10 Differences between Sales Cloud and CPQ, Blanka DoktorováCzechDreamin
 
Buy Epson EcoTank L3210 Colour Printer Online.pptx
Buy Epson EcoTank L3210 Colour Printer Online.pptxBuy Epson EcoTank L3210 Colour Printer Online.pptx
Buy Epson EcoTank L3210 Colour Printer Online.pptxEasyPrinterHelp
 
AI revolution and Salesforce, Jiří Karpíšek
AI revolution and Salesforce, Jiří KarpíšekAI revolution and Salesforce, Jiří Karpíšek
AI revolution and Salesforce, Jiří KarpíšekCzechDreamin
 
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...FIDO Alliance
 
Introduction to Open Source RAG and RAG Evaluation
Introduction to Open Source RAG and RAG EvaluationIntroduction to Open Source RAG and RAG Evaluation
Introduction to Open Source RAG and RAG EvaluationZilliz
 
AI presentation and introduction - Retrieval Augmented Generation RAG 101
AI presentation and introduction - Retrieval Augmented Generation RAG 101AI presentation and introduction - Retrieval Augmented Generation RAG 101
AI presentation and introduction - Retrieval Augmented Generation RAG 101vincent683379
 
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlFuture Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlPeter Udo Diehl
 

Recently uploaded (20)

Syngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdfSyngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdf
 
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdfHow Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
 
Intro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджераIntro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджера
 
Agentic RAG What it is its types applications and implementation.pdf
Agentic RAG What it is its types applications and implementation.pdfAgentic RAG What it is its types applications and implementation.pdf
Agentic RAG What it is its types applications and implementation.pdf
 
Powerful Start- the Key to Project Success, Barbara Laskowska
Powerful Start- the Key to Project Success, Barbara LaskowskaPowerful Start- the Key to Project Success, Barbara Laskowska
Powerful Start- the Key to Project Success, Barbara Laskowska
 
The Metaverse: Are We There Yet?
The  Metaverse:    Are   We  There  Yet?The  Metaverse:    Are   We  There  Yet?
The Metaverse: Are We There Yet?
 
Optimizing NoSQL Performance Through Observability
Optimizing NoSQL Performance Through ObservabilityOptimizing NoSQL Performance Through Observability
Optimizing NoSQL Performance Through Observability
 
IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024
 
Salesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
Salesforce Adoption – Metrics, Methods, and Motivation, Antone KomSalesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
Salesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
 
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
 
Designing for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at ComcastDesigning for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at Comcast
 
UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2
 
Free and Effective: Making Flows Publicly Accessible, Yumi Ibrahimzade
Free and Effective: Making Flows Publicly Accessible, Yumi IbrahimzadeFree and Effective: Making Flows Publicly Accessible, Yumi Ibrahimzade
Free and Effective: Making Flows Publicly Accessible, Yumi Ibrahimzade
 
10 Differences between Sales Cloud and CPQ, Blanka Doktorová
10 Differences between Sales Cloud and CPQ, Blanka Doktorová10 Differences between Sales Cloud and CPQ, Blanka Doktorová
10 Differences between Sales Cloud and CPQ, Blanka Doktorová
 
Buy Epson EcoTank L3210 Colour Printer Online.pptx
Buy Epson EcoTank L3210 Colour Printer Online.pptxBuy Epson EcoTank L3210 Colour Printer Online.pptx
Buy Epson EcoTank L3210 Colour Printer Online.pptx
 
AI revolution and Salesforce, Jiří Karpíšek
AI revolution and Salesforce, Jiří KarpíšekAI revolution and Salesforce, Jiří Karpíšek
AI revolution and Salesforce, Jiří Karpíšek
 
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
 
Introduction to Open Source RAG and RAG Evaluation
Introduction to Open Source RAG and RAG EvaluationIntroduction to Open Source RAG and RAG Evaluation
Introduction to Open Source RAG and RAG Evaluation
 
AI presentation and introduction - Retrieval Augmented Generation RAG 101
AI presentation and introduction - Retrieval Augmented Generation RAG 101AI presentation and introduction - Retrieval Augmented Generation RAG 101
AI presentation and introduction - Retrieval Augmented Generation RAG 101
 
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlFuture Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
 

Finishing the App - Part 1 - Transcript.pdf

  • 1. Finishing the App - Part I In this section we’ll finish the restaurant app while skipping some big pieces such as the implementation of the server
  • 2. The Server ✦The server code is attached to this course but I’m not discussing it ✦I want to avoid that complexity in this course and I’m keeping it for the app builder course © Codename One 2017 all rights reserved I’m attaching the server project to the module so you would be able to reference and probably run it but the goal of this course is to teach Codename One and not to teach server development. In the next course we’ll cover sever development more in depth and touch on some of the work we did for this server.
  • 3. Finishing the App ✦Finishing an app is the hardest thing for most developers ✦We procrastinate and find other things that need doing and can’t make it “past the finish line” ✦The trick is to decide on a very restricted list of features and work thru them until the beta ✦Don’t add to that list!
 It needs to be complete © Codename One 2017 all rights reserved The goal of this module is to finish the app. It’s not a goal to create a perfect app but rather to reach a milestone that we can use. One of the hardest things for developers to do is “finish”. We constantly find something new that needs doing and it becomes harder to reach a point of release. That’s why it’s crucial to define strict limits and curb your ambition so you can get something into the hands of users. Restricting the feature set to a strict list only works if you maintain discipline and don’t embellish the list. Don’t add to the entries and don’t add new entries.
  • 4. List of things to fix ✦Bind data to the server ✦Add support for delivery address collection ✦Add some validation such as minimum order ✦Fix styling of components for pressed states ✦Implement search in the client ✦Implement selection based on dish category ✦Store dish image data in the server/database and fetch/ cache dynamically © Codename One 2017 all rights reserved This is the current list I came up with for this app, it includes many missing pieces and some of them are pretty big but generally it represents the things we need to add or fix. I intend to implement all of these within this module which is relatively quick. Normally a list like this should take longer to implement.
  • 5. public class DishService { public static boolean hasCachedDishList() { return Storage.getInstance().exists("DishListCache"); } public static void loadDishesFromCache() { JSONParser p = new JSONParser(); try { Map<String, Object> m = p.parseJSON(new InputStreamReader( Storage.getInstance().createInputStream("DishListCache"), "UTF-8")); List<Object> l = (List<Object>)m.get("dishes"); Restaurant.getInstance().menu.get().dishes.clear(); for(Object o : l) { Dish d = new Dish(); d.getPropertyIndex().populateFromMap((Map)o); Restaurant.getInstance().menu.get().dishes.add(d); } List<String> cats = (List<String>)m.get("categories"); Restaurant.getInstance().menu.get().categories.clear(); for(String str : cats) { Restaurant.getInstance().menu.get().categories.add(str); } if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) { Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE); } } catch(IOException err) { Log.e(err); ToastBar.showErrorMessage("Error loading list of dishes: " + err); Log.sendLog(); DishService (Client) Lets get started with the first item on the agenda the network layer and I’ll do this by creating an abstraction of the server communication. Despite skipping the server code most of this code should be pretty clear as we go over it.
  • 6. public class DishService { public static boolean hasCachedDishList() { return Storage.getInstance().exists("DishListCache"); } public static void loadDishesFromCache() { JSONParser p = new JSONParser(); try { Map<String, Object> m = p.parseJSON(new InputStreamReader( Storage.getInstance().createInputStream("DishListCache"), "UTF-8")); List<Object> l = (List<Object>)m.get("dishes"); Restaurant.getInstance().menu.get().dishes.clear(); for(Object o : l) { Dish d = new Dish(); d.getPropertyIndex().populateFromMap((Map)o); Restaurant.getInstance().menu.get().dishes.add(d); } List<String> cats = (List<String>)m.get("categories"); Restaurant.getInstance().menu.get().categories.clear(); for(String str : cats) { Restaurant.getInstance().menu.get().categories.add(str); } if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) { Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE); } } catch(IOException err) { Log.e(err); ToastBar.showErrorMessage("Error loading list of dishes: " + err); Log.sendLog(); DishService (Client) The dish service implements caching for the dish list we fetch from the server to make sure the app loads instantly. If we fetch new data we will show it later as it’s more important that the app loads quickly. Here we check whether cache even exists, this is important for first time activation.
  • 7. public class DishService { public static boolean hasCachedDishList() { return Storage.getInstance().exists("DishListCache"); } public static void loadDishesFromCache() { JSONParser p = new JSONParser(); try { Map<String, Object> m = p.parseJSON(new InputStreamReader( Storage.getInstance().createInputStream("DishListCache"), "UTF-8")); List<Object> l = (List<Object>)m.get("dishes"); Restaurant.getInstance().menu.get().dishes.clear(); for(Object o : l) { Dish d = new Dish(); d.getPropertyIndex().populateFromMap((Map)o); Restaurant.getInstance().menu.get().dishes.add(d); } List<String> cats = (List<String>)m.get("categories"); Restaurant.getInstance().menu.get().categories.clear(); for(String str : cats) { Restaurant.getInstance().menu.get().categories.add(str); } if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) { Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE); } } catch(IOException err) { Log.e(err); ToastBar.showErrorMessage("Error loading list of dishes: " + err); Log.sendLog(); DishService (Client) Assuming cache exists we can just load the data from the cache into the global restaurant object. The cache has the JSON as it was returned from the server so we can parse it using the standard parsing code. This is convenient for debugging as we can inspect the json file in the filesystem for the simulator and see what’s saved locally
  • 8. public class DishService { public static boolean hasCachedDishList() { return Storage.getInstance().exists("DishListCache"); } public static void loadDishesFromCache() { JSONParser p = new JSONParser(); try { Map<String, Object> m = p.parseJSON(new InputStreamReader( Storage.getInstance().createInputStream("DishListCache"), "UTF-8")); List<Object> l = (List<Object>)m.get("dishes"); Restaurant.getInstance().menu.get().dishes.clear(); for(Object o : l) { Dish d = new Dish(); d.getPropertyIndex().populateFromMap((Map)o); Restaurant.getInstance().menu.get().dishes.add(d); } List<String> cats = (List<String>)m.get("categories"); Restaurant.getInstance().menu.get().categories.clear(); for(String str : cats) { Restaurant.getInstance().menu.get().categories.add(str); } if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) { Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE); } } catch(IOException err) { Log.e(err); ToastBar.showErrorMessage("Error loading list of dishes: " + err); Log.sendLog(); DishService (Client) The JSON file contains a map object which contains two arrays dishes and categories. The dishes array contains all the information in a dish and every entry within this map has the same key value pairs as the Dish object properties. One of the features of property objects is the ability to populate a property from a Map object or from JSON directly and this is exactly the case for which this is useful
  • 9. public class DishService { public static boolean hasCachedDishList() { return Storage.getInstance().exists("DishListCache"); } public static void loadDishesFromCache() { JSONParser p = new JSONParser(); try { Map<String, Object> m = p.parseJSON(new InputStreamReader( Storage.getInstance().createInputStream("DishListCache"), "UTF-8")); List<Object> l = (List<Object>)m.get("dishes"); Restaurant.getInstance().menu.get().dishes.clear(); for(Object o : l) { Dish d = new Dish(); d.getPropertyIndex().populateFromMap((Map)o); Restaurant.getInstance().menu.get().dishes.add(d); } List<String> cats = (List<String>)m.get("categories"); Restaurant.getInstance().menu.get().categories.clear(); for(String str : cats) { Restaurant.getInstance().menu.get().categories.add(str); } if(!Restaurant.getInstance().menu.get().dishDownloadFinished.get()) { Restaurant.getInstance().menu.get().dishDownloadFinished.set(Boolean.TRUE); } } catch(IOException err) { Log.e(err); ToastBar.showErrorMessage("Error loading list of dishes: " + err); Log.sendLog(); DishService (Client) The categories are just string names of all the available categories. We could probably just go over the dishes and extract the categories from there though. Once we add the categories we set the boolean flag indicating the download was finished, this is important as external code might have a listener bound to the property so it can refresh the UI once the data finished loading.
  • 10. public static void updateDishesFromServerSync() { NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer()); } public static void updateDishesFromServerAsync() { NetworkManager.getInstance().addToQueue(updateDishesFromServer()); } private static ConnectionRequest updateDishesFromServer() { ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) { private boolean autoLoad; protected void readResponse(InputStream input) throws IOException { JSONParser p = new JSONParser(); Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8")); String stamp = (String)m.get("stamp"); if(stamp != null) { Preferences.set("lastTimeStamp", stamp); OutputStream o = Storage.getInstance().createOutputStream("DishListCache"); o.write(Result.fromContent(m).toString().getBytes()); o.close(); autoLoad = true; } } protected void postResponse() { if(autoLoad) { loadDishesFromCache(); } } }; cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0")); cr.addArgument("appId", MyRestaurant.APP_AUTH); return cr; DishService (Client) Moving on we have two separate methods to update the dishes. They are both identical and just use the addToQueueAndWait or addToQueue. The reason we need both is simple, we use addToQueue when the app launches as we want to launch right away and don’t want to wait for the data. However, pull to refresh needs to block until the data is here and in that case we need to use addToQueueAndWait.
  • 11. public static void updateDishesFromServerSync() { NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer()); } public static void updateDishesFromServerAsync() { NetworkManager.getInstance().addToQueue(updateDishesFromServer()); } private static ConnectionRequest updateDishesFromServer() { ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) { private boolean autoLoad; protected void readResponse(InputStream input) throws IOException { JSONParser p = new JSONParser(); Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8")); String stamp = (String)m.get("stamp"); if(stamp != null) { Preferences.set("lastTimeStamp", stamp); OutputStream o = Storage.getInstance().createOutputStream("DishListCache"); o.write(Result.fromContent(m).toString().getBytes()); o.close(); autoLoad = true; } } protected void postResponse() { if(autoLoad) { loadDishesFromCache(); } } }; cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0")); cr.addArgument("appId", MyRestaurant.APP_AUTH); return cr; DishService (Client) The aptly named update dishes from server is the common code for both update methods, it creates a connection request to the dish URL that performs a GET HTTP method on the server.
  • 12. public static void updateDishesFromServerSync() { NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer()); } public static void updateDishesFromServerAsync() { NetworkManager.getInstance().addToQueue(updateDishesFromServer()); } private static ConnectionRequest updateDishesFromServer() { ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) { private boolean autoLoad; protected void readResponse(InputStream input) throws IOException { JSONParser p = new JSONParser(); Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8")); String stamp = (String)m.get("stamp"); if(stamp != null) { Preferences.set("lastTimeStamp", stamp); OutputStream o = Storage.getInstance().createOutputStream("DishListCache"); o.write(Result.fromContent(m).toString().getBytes()); o.close(); autoLoad = true; } } protected void postResponse() { if(autoLoad) { loadDishesFromCache(); } } }; cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0")); cr.addArgument("appId", MyRestaurant.APP_AUTH); return cr; DishService (Client) The response is parsed mostly so we can check the time stamp and update the local time stamp. The time stamp trick allows us to send a request with the last received time stamp and initially we just send 0. This means the server is responsible for updating that value and if no change was made since the last time the server changed that value we don’t need to receive new data.
  • 13. public static void updateDishesFromServerSync() { NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer()); } public static void updateDishesFromServerAsync() { NetworkManager.getInstance().addToQueue(updateDishesFromServer()); } private static ConnectionRequest updateDishesFromServer() { ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) { private boolean autoLoad; protected void readResponse(InputStream input) throws IOException { JSONParser p = new JSONParser(); Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8")); String stamp = (String)m.get("stamp"); if(stamp != null) { Preferences.set("lastTimeStamp", stamp); OutputStream o = Storage.getInstance().createOutputStream("DishListCache"); o.write(Result.fromContent(m).toString().getBytes()); o.close(); autoLoad = true; } } protected void postResponse() { if(autoLoad) { loadDishesFromCache(); } } }; cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0")); cr.addArgument("appId", MyRestaurant.APP_AUTH); return cr; DishService (Client) You will notice that if the data is changed we save it to cache then load it from cache later. This is pretty important, we call the loading code from the post response call which runs on the event dispatch thread. The other callbacks in the connection request occur on a network thread but the post response call is on the event dispatch thread and thus it can change the UI or UI related elements.
  • 14. public static void updateDishesFromServerSync() { NetworkManager.getInstance().addToQueueAndWait(updateDishesFromServer()); } public static void updateDishesFromServerAsync() { NetworkManager.getInstance().addToQueue(updateDishesFromServer()); } private static ConnectionRequest updateDishesFromServer() { ConnectionRequest cr = new ConnectionRequest(MyRestaurant.SERVER_URL + "dish", false) { private boolean autoLoad; protected void readResponse(InputStream input) throws IOException { JSONParser p = new JSONParser(); Map<String, Object> m = p.parseJSON(new InputStreamReader(input, "UTF-8")); String stamp = (String)m.get("stamp"); if(stamp != null) { Preferences.set("lastTimeStamp", stamp); OutputStream o = Storage.getInstance().createOutputStream("DishListCache"); o.write(Result.fromContent(m).toString().getBytes()); o.close(); autoLoad = true; } } protected void postResponse() { if(autoLoad) { loadDishesFromCache(); } } }; cr.addArgument("stamp", Preferences.get("lastTimeStamp", "0")); cr.addArgument("appId", MyRestaurant.APP_AUTH); return cr; DishService (Client) As I mentioned before we send the time stamp and also an authorization key to the server. The latter is hardcoded forthe app but the former gets updated by the server every time the data changes.
  • 15. @Override protected Container createContent() { Container c = new Container(BoxLayout.y()); final Menu m = Restaurant.getInstance().menu.get(); if(m.dishes.size() == 0) { if(DishService.hasCachedDishList()) { DishService.loadDishesFromCache(); } } if(m.dishes.size() > 0) { for(Dish currentDish : m.dishes) { c.add(createDishContainer(currentDish)); } c.addPullToRefresh(() -> { DishService.updateDishesFromServerSync(); c.removeAll(); for(Dish currentDish : m.dishes) { c.add(createDishContainer(currentDish)); } c.revalidate(); }); } else { MainMenuForm Let’s move on to the main menu form where we now fetch the dishes from cache if they aren’t loaded yet.
  • 16. @Override protected Container createContent() { Container c = new Container(BoxLayout.y()); final Menu m = Restaurant.getInstance().menu.get(); if(m.dishes.size() == 0) { if(DishService.hasCachedDishList()) { DishService.loadDishesFromCache(); } } if(m.dishes.size() > 0) { for(Dish currentDish : m.dishes) { c.add(createDishContainer(currentDish)); } c.addPullToRefresh(() -> { DishService.updateDishesFromServerSync(); c.removeAll(); for(Dish currentDish : m.dishes) { c.add(createDishContainer(currentDish)); } c.revalidate(); }); } else { MainMenuForm You will also notice that the pull to refresh call makes use of the update sync method to fetch the updated dishes synchronously.
  • 17. } else { final Container infi = FlowLayout.encloseCenter(new InfiniteProgress()); c.add(infi); m.dishDownloadFinished.addChangeListener(p -> { c.removeAll(); for(Dish currentDish : m.dishes) { c.add(createDishContainer(currentDish)); } categoryModel.removeAll(); for(String s : m.categories) { categoryModel.addItem(s); } c.revalidate(); }); } c.setScrollableY(true); c.setScrollVisible(false); dishesContainer = c; return c; } MainMenuForm This block represents a very special case of the first invocation when we have no dishes available and are still waiting. In this case we place an infinite progress in place and bind a listener to the finished download so we can replace the progress. Notice we aren’t concerned about dishes suddenly appearing in a background thread because everything is modified via the event dispatch thread so we can rely on this being in sync.