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 cookies
MahmoudOHassouna
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
Angular.js Primer in Aalto University
Angular.js Primer in Aalto UniversityAngular.js Primer in Aalto University
Angular.js Primer in Aalto University
SC5.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 toolbox
Shem Magnezi
 
Restaurant Server - Transcript.pdf
Restaurant Server - Transcript.pdfRestaurant Server - Transcript.pdf
Restaurant Server - Transcript.pdf
ShaiAlmog1
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
Sven Haiges
 
Android tutorials8 todo_list
Android tutorials8 todo_listAndroid tutorials8 todo_list
Android tutorials8 todo_list
Vlad 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 usefilter
Katy Slemon
 
Firebase ng2 zurich
Firebase ng2 zurichFirebase ng2 zurich
Firebase ng2 zurich
Christoffer Noring
 
Android Sample Project By Wael Almadhoun
Android Sample Project By Wael AlmadhounAndroid Sample Project By Wael Almadhoun
Android Sample Project By Wael Almadhoun
Wael Almadhoun, MSc, PMP®
 
Server Side Swift - AppBuilders 2017
Server Side Swift - AppBuilders 2017Server Side Swift - AppBuilders 2017
Server Side Swift - AppBuilders 2017
Jens 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 applications
Jeff 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 ListViews
Ahsanul Karim
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5
Elena Kolevska
 
Android tutorials8 todo_list
Android tutorials8 todo_listAndroid tutorials8 todo_list
Android tutorials8 todo_list
Vlad 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.pptx
Flutter Agency
 
Android best practices
Android best practicesAndroid best practices
Android best practices
Jose Manuel Ortega Candel
 

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.pdf
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
create-netflix-clone-01-introduction.pdf
create-netflix-clone-01-introduction.pdfcreate-netflix-clone-01-introduction.pdf
create-netflix-clone-01-introduction.pdf
ShaiAlmog1
 
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
ShaiAlmog1
 
create-netflix-clone-03-server.pdf
create-netflix-clone-03-server.pdfcreate-netflix-clone-03-server.pdf
create-netflix-clone-03-server.pdf
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
create-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdfcreate-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdf
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 
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
ShaiAlmog1
 

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

GraphSummit Singapore | The Future of Agility: Supercharging Digital Transfor...
GraphSummit Singapore | The Future of Agility: Supercharging Digital Transfor...GraphSummit Singapore | The Future of Agility: Supercharging Digital Transfor...
GraphSummit Singapore | The Future of Agility: Supercharging Digital Transfor...
Neo4j
 
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
SOFTTECHHUB
 
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
Neo4j
 
Essentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FMEEssentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FME
Safe Software
 
How to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For FlutterHow to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For Flutter
Daiki Mogmet Ito
 
20240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 202420240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 2024
Matthew Sinclair
 
Pushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 daysPushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 days
Adtran
 
Programming Foundation Models with DSPy - Meetup Slides
Programming Foundation Models with DSPy - Meetup SlidesProgramming Foundation Models with DSPy - Meetup Slides
Programming Foundation Models with DSPy - Meetup Slides
Zilliz
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
IndexBug
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
panagenda
 
Driving Business Innovation: Latest Generative AI Advancements & Success Story
Driving Business Innovation: Latest Generative AI Advancements & Success StoryDriving Business Innovation: Latest Generative AI Advancements & Success Story
Driving Business Innovation: Latest Generative AI Advancements & Success Story
Safe Software
 
Video Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the FutureVideo Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the Future
Alpen-Adria-Universität
 
Infrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI modelsInfrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI models
Zilliz
 
20240605 QFM017 Machine Intelligence Reading List May 2024
20240605 QFM017 Machine Intelligence Reading List May 202420240605 QFM017 Machine Intelligence Reading List May 2024
20240605 QFM017 Machine Intelligence Reading List May 2024
Matthew Sinclair
 
National Security Agency - NSA mobile device best practices
National Security Agency - NSA mobile device best practicesNational Security Agency - NSA mobile device best practices
National Security Agency - NSA mobile device best practices
Quotidiano Piemontese
 
Mind map of terminologies used in context of Generative AI
Mind map of terminologies used in context of Generative AIMind map of terminologies used in context of Generative AI
Mind map of terminologies used in context of Generative AI
Kumud Singh
 
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
Edge AI and Vision Alliance
 
Goodbye Windows 11: Make Way for Nitrux Linux 3.5.0!
Goodbye Windows 11: Make Way for Nitrux Linux 3.5.0!Goodbye Windows 11: Make Way for Nitrux Linux 3.5.0!
Goodbye Windows 11: Make Way for Nitrux Linux 3.5.0!
SOFTTECHHUB
 
UiPath Test Automation using UiPath Test Suite series, part 5
UiPath Test Automation using UiPath Test Suite series, part 5UiPath Test Automation using UiPath Test Suite series, part 5
UiPath Test Automation using UiPath Test Suite series, part 5
DianaGray10
 
Presentation of the OECD Artificial Intelligence Review of Germany
Presentation of the OECD Artificial Intelligence Review of GermanyPresentation of the OECD Artificial Intelligence Review of Germany
Presentation of the OECD Artificial Intelligence Review of Germany
innovationoecd
 

Recently uploaded (20)

GraphSummit Singapore | The Future of Agility: Supercharging Digital Transfor...
GraphSummit Singapore | The Future of Agility: Supercharging Digital Transfor...GraphSummit Singapore | The Future of Agility: Supercharging Digital Transfor...
GraphSummit Singapore | The Future of Agility: Supercharging Digital Transfor...
 
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
 
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
 
Essentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FMEEssentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FME
 
How to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For FlutterHow to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For Flutter
 
20240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 202420240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 2024
 
Pushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 daysPushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 days
 
Programming Foundation Models with DSPy - Meetup Slides
Programming Foundation Models with DSPy - Meetup SlidesProgramming Foundation Models with DSPy - Meetup Slides
Programming Foundation Models with DSPy - Meetup Slides
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
 
Driving Business Innovation: Latest Generative AI Advancements & Success Story
Driving Business Innovation: Latest Generative AI Advancements & Success StoryDriving Business Innovation: Latest Generative AI Advancements & Success Story
Driving Business Innovation: Latest Generative AI Advancements & Success Story
 
Video Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the FutureVideo Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the Future
 
Infrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI modelsInfrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI models
 
20240605 QFM017 Machine Intelligence Reading List May 2024
20240605 QFM017 Machine Intelligence Reading List May 202420240605 QFM017 Machine Intelligence Reading List May 2024
20240605 QFM017 Machine Intelligence Reading List May 2024
 
National Security Agency - NSA mobile device best practices
National Security Agency - NSA mobile device best practicesNational Security Agency - NSA mobile device best practices
National Security Agency - NSA mobile device best practices
 
Mind map of terminologies used in context of Generative AI
Mind map of terminologies used in context of Generative AIMind map of terminologies used in context of Generative AI
Mind map of terminologies used in context of Generative AI
 
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
 
Goodbye Windows 11: Make Way for Nitrux Linux 3.5.0!
Goodbye Windows 11: Make Way for Nitrux Linux 3.5.0!Goodbye Windows 11: Make Way for Nitrux Linux 3.5.0!
Goodbye Windows 11: Make Way for Nitrux Linux 3.5.0!
 
UiPath Test Automation using UiPath Test Suite series, part 5
UiPath Test Automation using UiPath Test Suite series, part 5UiPath Test Automation using UiPath Test Suite series, part 5
UiPath Test Automation using UiPath Test Suite series, part 5
 
Presentation of the OECD Artificial Intelligence Review of Germany
Presentation of the OECD Artificial Intelligence Review of GermanyPresentation of the OECD Artificial Intelligence Review of Germany
Presentation of the OECD Artificial Intelligence Review of Germany
 

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.