SlideShare a Scribd company logo
Miscellaneous Features - Part II
We’ll move on to some additional forms starting with the billing form
public class BillingForm extends BaseNavigationForm {
private Builder bld = new Builder();
public BillingForm(AppSettings app) {
super(app, new BorderLayout());
Container content = new Container(BoxLayout.y());
TableLayout tl = new TableLayout(2, 2);
tl.setGrowHorizontally(true);
Container table = new Container(tl);
Restaurant r = Restaurant.getInstance();
TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY);
TextField minimumOrder = new TextField("" + r.minimumOrder.get(),
"Minimum Order", 5, TextField.DECIMAL);
TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(),
"Delivery Range (KM)", 5, TextField.DECIMAL);
TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(),
"Delivery Surcharge", 5, TextField.DECIMAL);
currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText()));
minimumOrder.addActionListener(e ->
r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get())));
deliveryRangeKM.addActionListener(e ->
r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get())));
deliverySurcharge.addActionListener(e ->
r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get())));
BillingForm
The billing form like the details form is a relatively simple cookie cutter form. Today I might have made it with the InstantUI and would have been done with it but when I
wrote this code we didn’t have that class yet.
public class BillingForm extends BaseNavigationForm {
private Builder bld = new Builder();
public BillingForm(AppSettings app) {
super(app, new BorderLayout());
Container content = new Container(BoxLayout.y());
TableLayout tl = new TableLayout(2, 2);
tl.setGrowHorizontally(true);
Container table = new Container(tl);
Restaurant r = Restaurant.getInstance();
TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY);
TextField minimumOrder = new TextField("" + r.minimumOrder.get(),
"Minimum Order", 5, TextField.DECIMAL);
TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(),
"Delivery Range (KM)", 5, TextField.DECIMAL);
TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(),
"Delivery Surcharge", 5, TextField.DECIMAL);
currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText()));
minimumOrder.addActionListener(e ->
r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get())));
deliveryRangeKM.addActionListener(e ->
r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get())));
deliverySurcharge.addActionListener(e ->
r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get())));
BillingForm
Despite the fact that the UI looks simple the layout is non-trivial. First we have a border layout that encloses the form. We use that to keep the green button at the bottom
of the form regardless of size or scrolling
public class BillingForm extends BaseNavigationForm {
private Builder bld = new Builder();
public BillingForm(AppSettings app) {
super(app, new BorderLayout());
Container content = new Container(BoxLayout.y());
TableLayout tl = new TableLayout(2, 2);
tl.setGrowHorizontally(true);
Container table = new Container(tl);
Restaurant r = Restaurant.getInstance();
TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY);
TextField minimumOrder = new TextField("" + r.minimumOrder.get(),
"Minimum Order", 5, TextField.DECIMAL);
TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(),
"Delivery Range (KM)", 5, TextField.DECIMAL);
TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(),
"Delivery Surcharge", 5, TextField.DECIMAL);
currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText()));
minimumOrder.addActionListener(e ->
r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get())));
deliveryRangeKM.addActionListener(e ->
r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get())));
deliverySurcharge.addActionListener(e ->
r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get())));
BillingForm
Next we have the content container which is a box Y container that includes the whole UI except for the button. We mark it as Y scrollable further down in the code so we
can scroll thru the elements within. 

It’s important that text fields always reside in a Y scrollable container so the virtual keyboard will have space to work with.
public class BillingForm extends BaseNavigationForm {
private Builder bld = new Builder();
public BillingForm(AppSettings app) {
super(app, new BorderLayout());
Container content = new Container(BoxLayout.y());
TableLayout tl = new TableLayout(2, 2);
tl.setGrowHorizontally(true);
Container table = new Container(tl);
Restaurant r = Restaurant.getInstance();
TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY);
TextField minimumOrder = new TextField("" + r.minimumOrder.get(),
"Minimum Order", 5, TextField.DECIMAL);
TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(),
"Delivery Range (KM)", 5, TextField.DECIMAL);
TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(),
"Delivery Surcharge", 5, TextField.DECIMAL);
currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText()));
minimumOrder.addActionListener(e ->
r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get())));
deliveryRangeKM.addActionListener(e ->
r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get())));
deliverySurcharge.addActionListener(e ->
r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get())));
BillingForm
And finally we have the table. The numeric values are too small to give them each a line so I packed them in a 2 by 2 table. This entire table is added to the content later
on. Notice that the table container isn’t scrollable as nesting scrollables in mobile is a bad idea… 

We don’t have scrollbars so when we swipe the implementation needs to guess which scrollable we actually mean and that might not end well.
public class BillingForm extends BaseNavigationForm {
private Builder bld = new Builder();
public BillingForm(AppSettings app) {
super(app, new BorderLayout());
Container content = new Container(BoxLayout.y());
TableLayout tl = new TableLayout(2, 2);
tl.setGrowHorizontally(true);
Container table = new Container(tl);
Restaurant r = Restaurant.getInstance();
TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY);
TextField minimumOrder = new TextField("" + r.minimumOrder.get(),
"Minimum Order", 5, TextField.DECIMAL);
TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(),
"Delivery Range (KM)", 5, TextField.DECIMAL);
TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(),
"Delivery Surcharge", 5, TextField.DECIMAL);
currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText()));
minimumOrder.addActionListener(e ->
r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get())));
deliveryRangeKM.addActionListener(e ->
r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get())));
deliverySurcharge.addActionListener(e ->
r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get())));
BillingForm
As I mentioned before this code predated the binding API and didn’t use it. So we have some helper code such as the asDouble method which automatically parses the
text value and if it’s not a double returns the existing value. This allows us to automatically update the property. Binding would have made this code simpler and shorter
table.addAll(
BoxLayout.encloseY(new Label("Currency Symbol", “TextFieldLabel"), currencySymbol),
BoxLayout.encloseY(new Label("Minimum Order", “TextFieldLabel"), minimumOrder),
BoxLayout.encloseY(new Label("Delivery Range (KM)", “TextFieldLabel"), deliveryRangeKM),
BoxLayout.encloseY(new Label("Delivery Surcharge", “TextFieldLabel"), deliverySurcharge)
);
content.add(table);
Label separator = new Label("", "Separator");
separator.setShowEvenIfBlank(true);
content.add(separator);
content.add(new Label("Braintree Merchant Details"));
TextField merchantId = new TextField(app.merchantId.get(), "Merchant ID");
TextField publicKey = new TextField(app.publicKey.get(), "Public Key");
TextField privateKey = new TextField(app.privateKey.get(), "Private Key");
merchantId.addActionListener(e -> {
app.merchantId.set(merchantId.getText());
AppStorage.getInstance().update(app);
});
publicKey.addActionListener(e -> {
app.publicKey.set(publicKey.getText());
AppStorage.getInstance().update(app);
});
privateKey.addActionListener(e -> {
app.privateKey.set(privateKey.getText());
AppStorage.getInstance().update(app);
});
BillingForm
Next we add the elements to the table. I’m not really sure why I chose to do a 2 by 2 table with box layout instead of a 2 by 4 layout so we can place everything directly
into the table. Possibly it was force of habit, both approaches work though.
table.addAll(
BoxLayout.encloseY(new Label("Currency Symbol", “TextFieldLabel"), currencySymbol),
BoxLayout.encloseY(new Label("Minimum Order", “TextFieldLabel"), minimumOrder),
BoxLayout.encloseY(new Label("Delivery Range (KM)", “TextFieldLabel"), deliveryRangeKM),
BoxLayout.encloseY(new Label("Delivery Surcharge", “TextFieldLabel"), deliverySurcharge)
);
content.add(table);
Label separator = new Label("", "Separator");
separator.setShowEvenIfBlank(true);
content.add(separator);
content.add(new Label("Braintree Merchant Details"));
TextField merchantId = new TextField(app.merchantId.get(), "Merchant ID");
TextField publicKey = new TextField(app.publicKey.get(), "Public Key");
TextField privateKey = new TextField(app.privateKey.get(), "Private Key");
merchantId.addActionListener(e -> {
app.merchantId.set(merchantId.getText());
AppStorage.getInstance().update(app);
});
publicKey.addActionListener(e -> {
app.publicKey.set(publicKey.getText());
AppStorage.getInstance().update(app);
});
privateKey.addActionListener(e -> {
app.privateKey.set(privateKey.getText());
AppStorage.getInstance().update(app);
});
BillingForm
The separator line allows us to differentiate between the braintree arguments and the rest of the billing details. I’ve mentioned this before but it bares repeating. A
separator here is just a component with 1 pixel padding and a grayish color. Normally a label without text is hidden by default, but if we invoke set show even if blank it
will be rendered even if there is no text or icon
content.add(new Label("Merchant ID", "TextFieldLabel")).
add(merchantId).
add(new Label("Public Key", "TextFieldLabel")).
add(publicKey).
add(new Label("Private Key", "TextFieldLabel")).
add(privateKey);
content.setScrollableY(true);
add(BorderLayout.CENTER, content);
Button help = new Button("Learn About Billing", "GreenButton");
FontImage.setMaterialIcon(help, FontImage.MATERIAL_HELP);
add(BorderLayout.SOUTH, help);
}
double asDouble(String d, double def) {
try {
return Double.parseDouble(d);
} catch(NumberFormatException ne) {
return def;
}
}
BillingForm
There isn’t all that much here in the final part of the billing form, it’s just the things I’ve mentioned above like the scrollable Y of the content pane and its placement in the
center.
public void updateRestaurantSettings(AppSettings app) {
final String json = app.toJSONWithRestaurant();
ConnectionRequest cr = new ConnectionRequest(RestaurantAppBuilder.SERVER_URL +
"updateRestaurant", true) {
private String result;
@Override
protected void readResponse(InputStream input) throws IOException {
result = Util.readToString(input, "UTF-8");
}
@Override
protected void buildRequestBody(OutputStream os) throws IOException {
os.write(json.getBytes("UTF-8"));
}
};
cr.setContentType("application/json");
cr.setHttpMethod("PUT");
cr.setFailSilently(true);
cr.setReadResponseForErrors(true);
NetworkManager.getInstance().addToQueue(cr);
}
Builder
Now lets look at sending this data to the server side, up until now when we sent the data it didn’t include all the information we needed so we created a new version of
the app settings call with the restaurant details. 

This allows us to still reuse the existing objects from the restaurant model.
public String toJSONWithRestaurant() {
Map<String, Object> m = idx.toMapRepresentation();
m.putAll(Restaurant.getInstance().getPropertyIndex().toMapRepresentation());
m.remove(Restaurant.getInstance().cart.getName());
m.remove(Restaurant.getInstance().menu.getName());
m.remove("logo");
m.remove("titleBackground");
return Result.fromContent(m).toString();
}
AppSettings
This is a bit of a hack so lets go over what I did here. In the app settings class I get the value of the object as a map object. I then add all of the properties from the
restaurant object to create one large JSON object containing everything I need in the server which doesn’t have a distinction between the two.

Then I remove the cart and menu entries. These entries are big and I don’t need them for this specific API as they have their own model. I also remove the logo and title
background images as they have a separate webservice.
private void bindStringPropertyToPreference(Property<String, ? extends Object> p) {
p.set(Preferences.get(p.getName(), p.get()));
p.addChangeListener(pl -> Preferences.set(p.getName(), p.get()));
}
private void bindDoublePropertyToPreference(Property<Double, ? extends Object> p) {
p.set(Preferences.get(p.getName(), p.get()));
p.addChangeListener(pl -> Preferences.set(p.getName(), p.get()));
}
private Restaurant() {
bindStringPropertyToPreference(name);
bindStringPropertyToPreference(tagline);
bindDoublePropertyToPreference(latitude);
bindDoublePropertyToPreference(longitude);
bindStringPropertyToPreference(navigationAddress);
bindStringPropertyToPreference(address);
bindStringPropertyToPreference(phone);
bindStringPropertyToPreference(website);
bindStringPropertyToPreference(currency);
bindDoublePropertyToPreference(minimumOrder);
bindDoublePropertyToPreference(shippingRangeKM);
bindDoublePropertyToPreference(deliveryExtraCost);
}
public static Restaurant getInstance() {
if(instance == null) {
instance = new Restaurant();
}
return instance;
Persisting Restaurant to Storage
You might recall from before that basic things like the title, tagline etc. weren’t persisted anywhere… Well now we have a bind to preferences method that just implicitly
saves and restores from preferences. That way restaurant data is implicitly saved locally in preferences.

It’s not sqlite but it works and saves some of the effort, today after the improvements we have in the sqlmap I might have used that or might have used the seamless
preferences persistence we have built in.

More Related Content

PDF
Miscellaneous Features - Part 2.pdf
PDF
Finishing the App - Part 2.pdf
PDF
Miscellaneous Features - Part 1 - Transcript.pdf
PDF
Initial UI Mockup - Part 2.pdf
PDF
Pragmatic functional refactoring with java 8
PDF
help me Java projectI put problem and my own code in the linkmy .pdf
PDF
Extracting ui Design - part 4.pdf
PDF
Extracting ui Design - part 4 - transcript.pdf
Miscellaneous Features - Part 2.pdf
Finishing the App - Part 2.pdf
Miscellaneous Features - Part 1 - Transcript.pdf
Initial UI Mockup - Part 2.pdf
Pragmatic functional refactoring with java 8
help me Java projectI put problem and my own code in the linkmy .pdf
Extracting ui Design - part 4.pdf
Extracting ui Design - part 4 - transcript.pdf

Similar to Miscellaneous Features - Part 2 - Transcript.pdf (20)

PDF
UI Design From Scratch - Part 5.pdf
PDF
Miscellaneous Features - Part 1.pdf
PDF
Dutch php a short tale about state machine
PDF
Function Procedure Trigger Partition.pdf
PDF
JDD2014: Real life lambdas - Peter Lawrey
PDF
Below is my code- I have an error that I still have difficulty figurin.pdf
PDF
Architecture - Part 2 - Transcript.pdf
PDF
Creating a Facebook Clone - Part XXIX - Transcript.pdf
PDF
Creating an Uber - Part VI - Transcript.pdf
PDF
Creating an Uber Clone - Part XIV - Transcript.pdf
PDF
Creating a Facebook Clone - Part XXVIII - Transcript.pdf
PPT
C++ Functions.ppt
PPT
power point presentation on object oriented programming functions concepts
PDF
Creating an Uber Clone - Part III.pdf
PDF
Laziness, trampolines, monoids and other functional amenities: this is not yo...
DOCX
srcCommissionCalculation.javasrcCommissionCalculation.javaimpo.docx
PDF
Pragmatic functional refactoring with java 8 (1)
DOCX
project
PPT
Streams and lambdas the good, the bad and the ugly
PDF
UI Design From Scratch - Part 5 - transcript.pdf
UI Design From Scratch - Part 5.pdf
Miscellaneous Features - Part 1.pdf
Dutch php a short tale about state machine
Function Procedure Trigger Partition.pdf
JDD2014: Real life lambdas - Peter Lawrey
Below is my code- I have an error that I still have difficulty figurin.pdf
Architecture - Part 2 - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdf
Creating an Uber - Part VI - Transcript.pdf
Creating an Uber Clone - Part XIV - Transcript.pdf
Creating a Facebook Clone - Part XXVIII - Transcript.pdf
C++ Functions.ppt
power point presentation on object oriented programming functions concepts
Creating an Uber Clone - Part III.pdf
Laziness, trampolines, monoids and other functional amenities: this is not yo...
srcCommissionCalculation.javasrcCommissionCalculation.javaimpo.docx
Pragmatic functional refactoring with java 8 (1)
project
Streams and lambdas the good, the bad and the ugly
UI Design From Scratch - Part 5 - transcript.pdf
Ad

More from ShaiAlmog1 (20)

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

Recently uploaded (20)

PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Machine learning based COVID-19 study performance prediction
PPTX
Programs and apps: productivity, graphics, security and other tools
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Approach and Philosophy of On baking technology
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
cuic standard and advanced reporting.pdf
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Encapsulation theory and applications.pdf
PDF
Empathic Computing: Creating Shared Understanding
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PPTX
Spectroscopy.pptx food analysis technology
Digital-Transformation-Roadmap-for-Companies.pptx
Dropbox Q2 2025 Financial Results & Investor Presentation
Advanced methodologies resolving dimensionality complications for autism neur...
The Rise and Fall of 3GPP – Time for a Sabbatical?
Machine learning based COVID-19 study performance prediction
Programs and apps: productivity, graphics, security and other tools
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Spectral efficient network and resource selection model in 5G networks
Unlocking AI with Model Context Protocol (MCP)
Approach and Philosophy of On baking technology
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
cuic standard and advanced reporting.pdf
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Building Integrated photovoltaic BIPV_UPV.pdf
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Encapsulation theory and applications.pdf
Empathic Computing: Creating Shared Understanding
Mobile App Security Testing_ A Comprehensive Guide.pdf
Spectroscopy.pptx food analysis technology

Miscellaneous Features - Part 2 - Transcript.pdf

  • 1. Miscellaneous Features - Part II We’ll move on to some additional forms starting with the billing form
  • 2. public class BillingForm extends BaseNavigationForm { private Builder bld = new Builder(); public BillingForm(AppSettings app) { super(app, new BorderLayout()); Container content = new Container(BoxLayout.y()); TableLayout tl = new TableLayout(2, 2); tl.setGrowHorizontally(true); Container table = new Container(tl); Restaurant r = Restaurant.getInstance(); TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY); TextField minimumOrder = new TextField("" + r.minimumOrder.get(), "Minimum Order", 5, TextField.DECIMAL); TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(), "Delivery Range (KM)", 5, TextField.DECIMAL); TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(), "Delivery Surcharge", 5, TextField.DECIMAL); currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText())); minimumOrder.addActionListener(e -> r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get()))); deliveryRangeKM.addActionListener(e -> r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get()))); deliverySurcharge.addActionListener(e -> r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get()))); BillingForm The billing form like the details form is a relatively simple cookie cutter form. Today I might have made it with the InstantUI and would have been done with it but when I wrote this code we didn’t have that class yet.
  • 3. public class BillingForm extends BaseNavigationForm { private Builder bld = new Builder(); public BillingForm(AppSettings app) { super(app, new BorderLayout()); Container content = new Container(BoxLayout.y()); TableLayout tl = new TableLayout(2, 2); tl.setGrowHorizontally(true); Container table = new Container(tl); Restaurant r = Restaurant.getInstance(); TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY); TextField minimumOrder = new TextField("" + r.minimumOrder.get(), "Minimum Order", 5, TextField.DECIMAL); TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(), "Delivery Range (KM)", 5, TextField.DECIMAL); TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(), "Delivery Surcharge", 5, TextField.DECIMAL); currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText())); minimumOrder.addActionListener(e -> r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get()))); deliveryRangeKM.addActionListener(e -> r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get()))); deliverySurcharge.addActionListener(e -> r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get()))); BillingForm Despite the fact that the UI looks simple the layout is non-trivial. First we have a border layout that encloses the form. We use that to keep the green button at the bottom of the form regardless of size or scrolling
  • 4. public class BillingForm extends BaseNavigationForm { private Builder bld = new Builder(); public BillingForm(AppSettings app) { super(app, new BorderLayout()); Container content = new Container(BoxLayout.y()); TableLayout tl = new TableLayout(2, 2); tl.setGrowHorizontally(true); Container table = new Container(tl); Restaurant r = Restaurant.getInstance(); TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY); TextField minimumOrder = new TextField("" + r.minimumOrder.get(), "Minimum Order", 5, TextField.DECIMAL); TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(), "Delivery Range (KM)", 5, TextField.DECIMAL); TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(), "Delivery Surcharge", 5, TextField.DECIMAL); currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText())); minimumOrder.addActionListener(e -> r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get()))); deliveryRangeKM.addActionListener(e -> r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get()))); deliverySurcharge.addActionListener(e -> r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get()))); BillingForm Next we have the content container which is a box Y container that includes the whole UI except for the button. We mark it as Y scrollable further down in the code so we can scroll thru the elements within. It’s important that text fields always reside in a Y scrollable container so the virtual keyboard will have space to work with.
  • 5. public class BillingForm extends BaseNavigationForm { private Builder bld = new Builder(); public BillingForm(AppSettings app) { super(app, new BorderLayout()); Container content = new Container(BoxLayout.y()); TableLayout tl = new TableLayout(2, 2); tl.setGrowHorizontally(true); Container table = new Container(tl); Restaurant r = Restaurant.getInstance(); TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY); TextField minimumOrder = new TextField("" + r.minimumOrder.get(), "Minimum Order", 5, TextField.DECIMAL); TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(), "Delivery Range (KM)", 5, TextField.DECIMAL); TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(), "Delivery Surcharge", 5, TextField.DECIMAL); currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText())); minimumOrder.addActionListener(e -> r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get()))); deliveryRangeKM.addActionListener(e -> r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get()))); deliverySurcharge.addActionListener(e -> r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get()))); BillingForm And finally we have the table. The numeric values are too small to give them each a line so I packed them in a 2 by 2 table. This entire table is added to the content later on. Notice that the table container isn’t scrollable as nesting scrollables in mobile is a bad idea… We don’t have scrollbars so when we swipe the implementation needs to guess which scrollable we actually mean and that might not end well.
  • 6. public class BillingForm extends BaseNavigationForm { private Builder bld = new Builder(); public BillingForm(AppSettings app) { super(app, new BorderLayout()); Container content = new Container(BoxLayout.y()); TableLayout tl = new TableLayout(2, 2); tl.setGrowHorizontally(true); Container table = new Container(tl); Restaurant r = Restaurant.getInstance(); TextField currencySymbol = new TextField(r.currency.get(), "Currency Symbol", 5, TextField.ANY); TextField minimumOrder = new TextField("" + r.minimumOrder.get(), "Minimum Order", 5, TextField.DECIMAL); TextField deliveryRangeKM = new TextField("" + r.shippingRangeKM.get(), "Delivery Range (KM)", 5, TextField.DECIMAL); TextField deliverySurcharge = new TextField("" + r.deliveryExtraCost.get(), "Delivery Surcharge", 5, TextField.DECIMAL); currencySymbol.addActionListener(e -> r.currency.set(currencySymbol.getText())); minimumOrder.addActionListener(e -> r.minimumOrder.set(asDouble(minimumOrder.getText(), r.minimumOrder.get()))); deliveryRangeKM.addActionListener(e -> r.shippingRangeKM.set(asDouble(deliveryRangeKM.getText(), r.shippingRangeKM.get()))); deliverySurcharge.addActionListener(e -> r.deliveryExtraCost.set(asDouble(deliverySurcharge.getText(), r.deliveryExtraCost.get()))); BillingForm As I mentioned before this code predated the binding API and didn’t use it. So we have some helper code such as the asDouble method which automatically parses the text value and if it’s not a double returns the existing value. This allows us to automatically update the property. Binding would have made this code simpler and shorter
  • 7. table.addAll( BoxLayout.encloseY(new Label("Currency Symbol", “TextFieldLabel"), currencySymbol), BoxLayout.encloseY(new Label("Minimum Order", “TextFieldLabel"), minimumOrder), BoxLayout.encloseY(new Label("Delivery Range (KM)", “TextFieldLabel"), deliveryRangeKM), BoxLayout.encloseY(new Label("Delivery Surcharge", “TextFieldLabel"), deliverySurcharge) ); content.add(table); Label separator = new Label("", "Separator"); separator.setShowEvenIfBlank(true); content.add(separator); content.add(new Label("Braintree Merchant Details")); TextField merchantId = new TextField(app.merchantId.get(), "Merchant ID"); TextField publicKey = new TextField(app.publicKey.get(), "Public Key"); TextField privateKey = new TextField(app.privateKey.get(), "Private Key"); merchantId.addActionListener(e -> { app.merchantId.set(merchantId.getText()); AppStorage.getInstance().update(app); }); publicKey.addActionListener(e -> { app.publicKey.set(publicKey.getText()); AppStorage.getInstance().update(app); }); privateKey.addActionListener(e -> { app.privateKey.set(privateKey.getText()); AppStorage.getInstance().update(app); }); BillingForm Next we add the elements to the table. I’m not really sure why I chose to do a 2 by 2 table with box layout instead of a 2 by 4 layout so we can place everything directly into the table. Possibly it was force of habit, both approaches work though.
  • 8. table.addAll( BoxLayout.encloseY(new Label("Currency Symbol", “TextFieldLabel"), currencySymbol), BoxLayout.encloseY(new Label("Minimum Order", “TextFieldLabel"), minimumOrder), BoxLayout.encloseY(new Label("Delivery Range (KM)", “TextFieldLabel"), deliveryRangeKM), BoxLayout.encloseY(new Label("Delivery Surcharge", “TextFieldLabel"), deliverySurcharge) ); content.add(table); Label separator = new Label("", "Separator"); separator.setShowEvenIfBlank(true); content.add(separator); content.add(new Label("Braintree Merchant Details")); TextField merchantId = new TextField(app.merchantId.get(), "Merchant ID"); TextField publicKey = new TextField(app.publicKey.get(), "Public Key"); TextField privateKey = new TextField(app.privateKey.get(), "Private Key"); merchantId.addActionListener(e -> { app.merchantId.set(merchantId.getText()); AppStorage.getInstance().update(app); }); publicKey.addActionListener(e -> { app.publicKey.set(publicKey.getText()); AppStorage.getInstance().update(app); }); privateKey.addActionListener(e -> { app.privateKey.set(privateKey.getText()); AppStorage.getInstance().update(app); }); BillingForm The separator line allows us to differentiate between the braintree arguments and the rest of the billing details. I’ve mentioned this before but it bares repeating. A separator here is just a component with 1 pixel padding and a grayish color. Normally a label without text is hidden by default, but if we invoke set show even if blank it will be rendered even if there is no text or icon
  • 9. content.add(new Label("Merchant ID", "TextFieldLabel")). add(merchantId). add(new Label("Public Key", "TextFieldLabel")). add(publicKey). add(new Label("Private Key", "TextFieldLabel")). add(privateKey); content.setScrollableY(true); add(BorderLayout.CENTER, content); Button help = new Button("Learn About Billing", "GreenButton"); FontImage.setMaterialIcon(help, FontImage.MATERIAL_HELP); add(BorderLayout.SOUTH, help); } double asDouble(String d, double def) { try { return Double.parseDouble(d); } catch(NumberFormatException ne) { return def; } } BillingForm There isn’t all that much here in the final part of the billing form, it’s just the things I’ve mentioned above like the scrollable Y of the content pane and its placement in the center.
  • 10. public void updateRestaurantSettings(AppSettings app) { final String json = app.toJSONWithRestaurant(); ConnectionRequest cr = new ConnectionRequest(RestaurantAppBuilder.SERVER_URL + "updateRestaurant", true) { private String result; @Override protected void readResponse(InputStream input) throws IOException { result = Util.readToString(input, "UTF-8"); } @Override protected void buildRequestBody(OutputStream os) throws IOException { os.write(json.getBytes("UTF-8")); } }; cr.setContentType("application/json"); cr.setHttpMethod("PUT"); cr.setFailSilently(true); cr.setReadResponseForErrors(true); NetworkManager.getInstance().addToQueue(cr); } Builder Now lets look at sending this data to the server side, up until now when we sent the data it didn’t include all the information we needed so we created a new version of the app settings call with the restaurant details. This allows us to still reuse the existing objects from the restaurant model.
  • 11. public String toJSONWithRestaurant() { Map<String, Object> m = idx.toMapRepresentation(); m.putAll(Restaurant.getInstance().getPropertyIndex().toMapRepresentation()); m.remove(Restaurant.getInstance().cart.getName()); m.remove(Restaurant.getInstance().menu.getName()); m.remove("logo"); m.remove("titleBackground"); return Result.fromContent(m).toString(); } AppSettings This is a bit of a hack so lets go over what I did here. In the app settings class I get the value of the object as a map object. I then add all of the properties from the restaurant object to create one large JSON object containing everything I need in the server which doesn’t have a distinction between the two. Then I remove the cart and menu entries. These entries are big and I don’t need them for this specific API as they have their own model. I also remove the logo and title background images as they have a separate webservice.
  • 12. private void bindStringPropertyToPreference(Property<String, ? extends Object> p) { p.set(Preferences.get(p.getName(), p.get())); p.addChangeListener(pl -> Preferences.set(p.getName(), p.get())); } private void bindDoublePropertyToPreference(Property<Double, ? extends Object> p) { p.set(Preferences.get(p.getName(), p.get())); p.addChangeListener(pl -> Preferences.set(p.getName(), p.get())); } private Restaurant() { bindStringPropertyToPreference(name); bindStringPropertyToPreference(tagline); bindDoublePropertyToPreference(latitude); bindDoublePropertyToPreference(longitude); bindStringPropertyToPreference(navigationAddress); bindStringPropertyToPreference(address); bindStringPropertyToPreference(phone); bindStringPropertyToPreference(website); bindStringPropertyToPreference(currency); bindDoublePropertyToPreference(minimumOrder); bindDoublePropertyToPreference(shippingRangeKM); bindDoublePropertyToPreference(deliveryExtraCost); } public static Restaurant getInstance() { if(instance == null) { instance = new Restaurant(); } return instance; Persisting Restaurant to Storage You might recall from before that basic things like the title, tagline etc. weren’t persisted anywhere… Well now we have a bind to preferences method that just implicitly saves and restores from preferences. That way restaurant data is implicitly saved locally in preferences. It’s not sqlite but it works and saves some of the effort, today after the improvements we have in the sqlmap I might have used that or might have used the seamless preferences persistence we have built in.