Assignment 7

Welcome to week five of Mobile Systems and Applications! (cs.upt.ro/~alext/msa/lab7)

Topics of discussion

  • Data listeners for Firebase

  • Handling lists of data

  • Project planning & deadlines

Handling Firebase data snapshots

When working with Firebase's real-time database you have the option to attach listeners to different paths of the database. As the data is structured similar to a file-folder structure, you may well have more than one listener attached. For example, imagine partition C: in a Windows environment. It's like listening to file changes in, say, Program Files, My Documents and Downloads. Each listener works independently, and whenever it is triggered, it will provide a snapshot of the current data below the root of the listener.

Given the data structure below, imagine we attach two listeners, one on the key "wallet" and one on "february".

databaseReference.child("wallet").addValueEventListener(new ValueEventListener() {

@Override

public void onDataChange(DataSnapshot dataSnapshot) {

// dataSnaphot = json with key 'wallet'

}

@Override

public void onCancelled(DatabaseError databaseError) {

}

});

databaseReference.child("calendar").child("february")

.addValueEventListener(new ValueEventListener() {

@Override

public void onDataChange(DataSnapshot dataSnapshot) {

// datasnapshot = json with key 'february'

}

@Override

public void onCancelled(DatabaseError databaseError) {

}

});

The wallet-listener will trigger whenever any of the entries below it will change, while the february-listener will trigger only when an entry under the key changes (i.e. income or expenses, or a new key is added). The data snapshots provided in the onDataChange method, however, look completely different. The snapshot corresponding to "february" has the month's name as a key, and two more entries as a key-value pair. The data can be marshalled to an object in the following ways. The simpler way uses the getValue(ClassName.class) method and maps each field directly, while the second method accesses each field in turn with child("field name") and getValue(), and sets the class instance fields one by one.

@Override

public void onDataChange(DataSnapshot dataSnapshot) {

// simpler method

MonthlyExpense monthlyExpense1 = dataSnapshot.getValue(MonthlyExpense.class);

// OR

// field by field

MonthlyExpense monthlyExpense2 = new MonthlyExpense();

monthlyExpense2.setIncome((Float) dataSnapshot.child("income").getValue());

monthlyExpense2.setExpenses((Float) dataSnapshot.child("expenses").getValue());

// ...

}

The snapshot corresponding to "wallet" has a list of wallet entries, so we cannot do a direct mapping here. First we need to iterate over the children of this snapshot, and inside those children we may do a mapping to a WalletEntry class. For example, to access the costs of each entry from the snapshot, and sum them up, we would need to write something like this:

@Override

public void onDataChange(DataSnapshot dataSnapshot) {

double totalCost = 0;

// iterating over each wallet entry

for (DataSnapshot entrySnaphot : dataSnapshot.getChildren()) {

// checking if entry has cost defined

if (entrySnaphot.hasChild("cost"))

totalCost += (double) entrySnaphot.child("cost").getValue();

}

}

Make sure you read more about reading & writing data in Firebase from here.

Handling lists of data

Often you will have to deal with lists of data, rather than a single entry. Also you might be interested in the state of the list itself, so Firebase API provides another type of listener for this case. Child events are triggered in response to specific operations that happen to the children of a node from an operation such as a new child added or a child being updated. Each of these together can be useful for listening to changes to a specific node in a database. In order to listen for child events on DatabaseReference, attach a ChildEventListener.

databaseReference.child("calendar").addChildEventListener(new ChildEventListener() {

@Override

public void onChildAdded(DataSnapshot dataSnapshot, String s) {

/* Retrieve lists of items or listen for additions to a list of items.

This callback is triggered once for each existing child and then again every time

a new child is added to the specified path. The DataSnapshot passed to the listener

contains the new child's data. */

}

@Override

public void onChildChanged(DataSnapshot dataSnapshot, String s) {

/* Listen for changes to the items in a list. This event fired any time a child

node is modified, including any modifications to descendants of the child node.

The DataSnapshot passed to the event listener contains the updated data for the child.

*/

}

@Override

public void onChildRemoved(DataSnapshot dataSnapshot) {

/* Listen for items being removed from a list. The DataSnapshot passed to the event

callback contains the data for the removed child. */

}

@Override

public void onChildMoved(DataSnapshot dataSnapshot, String s) {

/* Listen for changes to the order of items in an ordered list. This event is

triggered whenever the onChildChanged() callback is triggered by an update that

causes reordering of the child. It is used with data that is ordered with

orderByChild or orderByValue. */

}

@Override

public void onCancelled(DatabaseError databaseError) {

}

});

Often you will want to retrieve data from the server only once, at application startup and not have to update the state of the application permanently. For example, if we read user preferences stored online, or any other type of private data that is not shared with anyone else, it is clear that those preferences will not be changed by anyone else at runtime, so it's safe to read them just once. For this purpose you may want a callback to be called once and then immediately removed, so you can use the addListenerForSingleValueEvent() method to simplify this scenario.

databaseReference.child("calendar").addListenerForSingleValueEvent(new ValueEventListener() {

@Override

public void onDataChange(DataSnapshot dataSnapshot) {

// called only once, when the listener is attached

}

@Override

public void onCancelled(DatabaseError databaseError) {

}

});

Project milestones

The final activity grade at this lab will be calculated as: 30% lab test + 70% project grade. The project should consist of a mobile application running on any mobile platform using any technology you wish. While we work on learning how to develop Android apps that run on phones and tablets, you may well develop your own project as an iOS or Windows Phone app, or as an Android app customized for a smartwatch or TV. Apart from this freedom you may also choose what to implement! Think of creating something new, reproducing an existing app inspired from the play store, or simply upgrade an app by reproducing it and adding features of your own. Here you can find some project ideas from which you make take inspiration.

The project milestones are as such:

  1. Week 8: present the project specifications. This step requires that your idea is already well settled, and are able to detail it using at least one of the following: use cases, Android activities (screens) diagram, UML diagram. The specifications and diagrams may be drawn by hand.

  2. Week 14: present the documentation & project. Following this guide, describe, motivate, compare and detail your project. The documentation represents 2 points of the project grade. This milestone is strict, and not meeting the deadline will incur important penalties.

Task #1

  • Using last week's smart Wallet application, instead of manually searching using the bSearch and eSearch views, use a spinner that is automatically updated with the list of months from Firebase (at runtime). The spinner may be used to switch between existing months. Manually inserting a new month in Firebase should update the spinner at runtime (optional), or after a relaunch of the app.

  • You may remove the search Button and Edittext and add a Spinner to your layout instead. Try creating something similar to Figure 1. Hint: use centerVertical for the LinearLayout, and align the other two views above and below it.

    • Fig.1.

  • Initialize the spinner similar to the way you did back in week 2. The spinner needs an adapter which holds a list (or array) of month names. These lines of code should get you started. Do not forget to attach a setOnItemSelectedListener.

                • // data structures

                • final List<MonthlyExpense> monthlyExpenses = new ArrayList<>();

                • final List<String> monthNames = new ArrayList<>();

                • // spinner adapter

                • final ArrayAdapter<String> sAdapter = new ArrayAdapter<>(this,

                • android.R.layout.simple_spinner_item, monthNames);

                • sAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

                • sSearch.setAdapter(sAdapter);

  • To populate the Spinner with the month names retrieved from Firebase, try to either attach a ValueEvent or ChildEvent listener on the "calendar" node. The difference lies within what data you get in the corresponding snapshots when onDataChange is triggered. Hint: the listener should be attached right away, in onCreate.

                  • databaseReference./**/addValueEventListener(new ValueEventListener() {

                  • @Override

                  • public void onDataChange(DataSnapshot dataSnapshot) {

                  • for (DataSnapshot monthSnapshot : dataSnapshot.getChildren()) {

                  • try {

                  • // create a new instance of MonthlyExpense

                  • // save the key as month name

                  • // save the month and month name

                  • } catch (Exception e) {

                  • }

                  • }

                  • // notify the spinner that data may have changed

                  • sAdapter.notifyDataSetChanged();

                  • }

                  • @Override

                  • public void onCancelled(DatabaseError databaseError) {

                  • }

                  • });

  • Implement the necessary code to change the income and expenses values using the bUpdate button. For a reference about writing to Firebase read here, and also ask the lab assistant for hints. Don't forget to check if the inputs of eIncome and eExpenses are non-empty, are parsable to a decimal number, and your database reference and current month are non-null. You can get the current month name from the spinner selected item name.

                  • private void updateDB() {

                  • // texts in eIncome and eExpenses are not empty

                  • if (/**/) {

                  • try {

                  • // parse data to double

                  • // get current month name from spinner

                  • String currentMonth = sSearch.getSelectedItem().toString();

                  • // create a Map<String, Object> for income and expenses

                  • // call update children on database reference

                  • if (/**/)

                  • /**/

                  • } catch (Exception e) {

                  • Toast.makeText(this, "Income and expenses must be in numeric format", Toast.LENGTH_SHORT).show();

                  • }

                  • } else {

                  • Toast.makeText(this, "Income and expenses fields cannot be empty!", Toast.LENGTH_SHORT).show();

                  • }

                  • }

  • There is one last issue: we may see data updates when changing between months, but if the current month changes its income or expenses values, the UI will not update itself. Fix this issue by adding the appropriate code in onDataChange, inside the for loop.

  • Homework: Use shared preferences to save to current month so that when you relaunch the app the spinner will be selected accordingly, and the income and expenses of that month will be visible. Note that the spinner selected item should be initialized with the appropriate month only after the data listener has triggered for the first time. Plus one activity (+1) point for the homework. Homework may only be presented individually, on your smartphone or emulator, with the code running on your PC.

Have a fruitful week.

Yuzu fruit

Yuzu fruit is a hybrid variety of mandarin and papeda. The fruit is very similar to grapefruit with an uneven skin. It's one of the most important fruit in Asia that ranges between 5.5 and 7.5 cm in diameter. The fruit is mainly cultivated in China, Korea and Japan. Yuzu fruits are very aromatic and might be yellow or green color depending on the degree of ripeness.

http://www.fruitsinfo.com/yuzu-hybrid-fruits.php