It has been a while since the last post due to a highly busy period! A lot of blog posts were postponed, but now
a series of posts will follow in these two weeks documenting most of the features that I have worked on the App InventorChart components project!
One of the core features of Chart Data Importing will be discussed in this blog post as a direct follow up to the post documenting
Data Import from Tuples. This post will primarily focus on
the introduced Data Source concepts for the Chart components, as well as TinyDB & CloudDB importing.
Data Source Concept
In order to somewhat generalize all the possible Chart Data Sources (as well as future additions), a design
choice was made to introduce the concept of a Data Source, which means that all possible Chart Data Sources
would implement from some interfaces and data retrieval would be very similar amongst all Data Sources.
Alongside that, the usage of Chart Data Sources for the Data component would be more or less highly
similar.
The following diagram illustrates the idea:
Note the Data Key Values argument used. This argument essentially allows to be selective about the data coming from the component, allowing
to import different sets of Data. For instance, in a Database, the key value would be a single Tag value which identifies entries. However,
there are also Data Sources which would require multiple tags, for example CSV Files, which would require specifying multiple column names.
The defined Data Source concept essentially allows to make data importing quite consistent, and makes it easier to implement
new compatible Data Sources in the future.
Data Source hierarchy
To make this concept work, the implementation essentially has to be based on interfaces. Each compatible Data Source should implement the
required corresponding Data Source interface to be identified as such.
While analyzing every currently possible Data Source, I have decided to group them into 3 categories:
Static Data Source - values are retrieved manually
Observable Data Source - Data components can observe the Data Source. Values can still be retrieved manually, but the Data Source
sends events when values change.
Real Time Data Source - Data components observe the Data Source. Data Sources can send new values to all observers.
While thinking of these 3 categories, I have also made an observation that they actually share some characteristics. For one,
all Data Sources can return values by manual retrieval, so in fact, the Observable and Real Time Data Sources are extensions
of the Static Data Source.
Another observation is between the Observable and Real Time Data Sources. In fact, they both exhibit the same behavior,
except that the Observable Data Source updates the values, and the Real Time Data Source sends new values. However,
from the point of view of the Data Source interface, these details will not matter, since the Data component will
be the one processing the data. Hence, this leads to the following hierarchy design:
Workflow
In terms of workflow, there are multiple ways to import Data from the Data Sources:
1.Directly via Designer blocks
2.Setting the Source property in Designer to the Data component (this also observes the Data Source)
3.Changing the Data Source via blocks (this also observes the Data Source)
Implementation
Let’s now look at some parts of the implementation. The first part that we will look at is realizing the proposed Data Source hierarchy in code.
Hierarchy implementation
As one might notice, the implementation exactly corresponds to the UML model. The interfaces were kept relatively simple,
containing only the necessary methods. Most of the heavy lifting will, in fact, be handled by the Data components themselves,
as it is up to them to interpret the Data coming from Data Sources.
One important part to note is the notifyDataObservers method. Since we want to generalize this as much as possible, the
ChartData component is complemented with an interface to retrieve the notification events:
In the implementation of the notifyDataObservers, the onDataSourceValueChange method needs to be called
for each Observer (as we will see later). For the implementation of the Real Time Chart Data Source,
a different interface will be introduced (to be shown in a subsequent blog post)
Property Setter
Due to the way that property setters and Object types are parsed right now in App Inventor, unfortunately some form of
hardcoding is still required. Since the Source property should only accept eligible Chart Data Sources, we need
to define a new Property (which we name PROOPERTY_TYPE_CHART_DATA_SOURCE) with its own Property Editor (this
is something in App Inventor that manages the workflow of selecting properties via the properties window in
the Designer).
The code for initializing the Property Editor looks as follows:
The details of the Property Editor are not as important, the important fact is that the constructor’s second
argument accepts a HashSet of eligible components. Since abstract types and interfaces are not recognized
by the parsing done on compilation, we must specify each component individually in the constant
HashSet.
TinyDB Importing
Let us now move on to a concrete example implementation of a Data Source - the TinyDB component. This is
an existing component that allows storing key-value pairs locally, and hence can be adapted to work as
a Data Source for the Charts.
Process
The data importing process is as follows:
Specifying data importing
The first step begins from specifying the data import to happen from the TinyDB component. This can be done in any of the 3 ways
mentioned before (directly via blocks, via properties or via changing Data Source via a block). The chosen format for values
for the TinyDB were that a single value (identified by a tag) corresponds to an entire data series, meaning that a single value
is a List of Lists, where each List entry corresponds to an entry of the data series, represented as a tuple. The format is as follows:
A list is represented using the round brackets. An example of a Data Series in this format is as follows:
In blocks, this looks as follows:
Note how in the TinyDB component, a single tag holds the entire List of Lists, which corresponds to the format described.
Data component value getting & passing
The next step is for the Data component to retrieve the value from the TinyDB component and pass it on to the Data model. In the
code, this looks as follows:
This is a snippet corresponding to the implemented ImportFromTinyDB block. The following steps occur:
The TinyDB and tag arguments are passed in to the method
The List value corresponding to the tag is retrieved from the TinyDB component
The current Data Source value is updated (this handles updating the value if the TinyDB component is
the currently observed Data Source)
The data is imported asynchronously (via the Chart Data model!), and the Chart is refreshed.
The implementation on TinyDB’s end is quite simple; The value is simply retrieved using the
existing GetValue method, the value is checked if it is a List (and returned if that is the case),
and then the value is returned. If the value is not a List, an empty List is simply returned (indicating
that no data should be imported)
We have already seen in a previous blog post what happens in the importFromList
method in the Chart Data Model, therefore we will not touch upon it again.
Observable TinyDB
It gets a bit more complicated when we implement the TinyDB component to be observable by the Chart. There are
quite a few important considerations to take into account when implementing observable Chart Data Sources.
They are as follows:
When the observable Data Source’s value changes, the old values need to be removed
If values are imported manually, they must be also tracked as an update by the observable Chart Data Source
Since the initialization of components’ order is not guaranteed, Chart components need to only start importing
from Data Sources after all components are initialized (in the case of property setting via Designer)
First, let’s take a look at the TinyDB side of the implementation (we will take a look at the Chart Data side
of the implementation in a later section), which is simpler than the Data component’s functionality
with regards to Data Sources:
First, let’s take a look at the interface’s implemented methods which add, remove and notify the Data Observers.
These are kept quite simple. The remove and add methods simply modify the Data Source Observers HashSet accordingly,
while the notifyDataObservers method simply notifies each Data Component with the appropriate key and newValue
object which corresponds to the change event being notified of.
Now, let’s move our way up to the ClearAll and onDelete methods. Since these operations do not invoke any
events whatsoever on the underlying SharedPreferences object, we must manually notify all the Data Observers.
We use a convention here – the null key means that all observers need to be notified (think of it as a global key),
and the null value simply indicates that the new value is non-existent (null).
Finally, the most important part of the implementation is the change in the Namespace method. We essentially
want to keep track of changes to the values in the underlying data structure, which is the SharedPreferences
object. We can accomplish this quite simply by making use of the OnSharedPreferencesChangeListener. Whenever
a value changes, the Listener gets invoked.
Since Namespace changes are possible, we only want to keep one such Listener at a time, so we unregister
the last listener whenever the Namespace changes. Then, we register a new one, which notifies all Data
Observers of the change with the new value (or null, if the value is not present).
The end result is that we have constructed a fully observable TinyDB component.
Application examples
Let us now look at some concrete examples in App Inventor itself.
Import via Blocks
First, let’s consider a simple example where data is imported via blocks.
The Designer window simply consists of a Chart component attached with a single, default-property Data component,
as well as a TinyDB component with its default properties. The blocks setup is as follows:
The result when starting the app is as follows:
Observable Data Example
Consider the following properties and block setup:
The following animation illustrates how the observed data keeps changing:
Chart Data component as an Observer
We have seen the TinyDB implementation as an Observable Chart Data Source, however, there is quite a large amount
of logic in the Chart Data component itself.
Below is the entirety of the code for the ChartDataBase class which covers observable data import support for TinyDB & CloudDB
(see below the code snippet for a walkthrough of the code):
Starting from the top of the snippet, we can see that the DataSourceValue setter is a very straightforward
operation. It is a Designer-only property, and the value is simply updated, to be used later when importing
from the specified Source component.
The Source setter works as follows:
First, the previous Data Source component is un-observed if it was an Observable Chart Data Source before
Then, the new Chart Data Source component is set
If the component is fully initialized, data importing logic is proceeded with.
If the DataSource is an observable Chart Data Source, the Data Component is added as an observer
Data is imported on a case-by-case basis (e.g. if the component is a TinyDB object, the Import From TinyDB option is used)
The initialized variable checks and the onInitialize method take care of the consideration for the ordering of components
to ensure that all properties are fully initialized and data can be imported with no errors. Another point to note is that
the ElementsFromPairs setter is completely ignored if a Data Source is present, and this is used to prevent data overriding.
The ElementsFromPairs on it’s own can be taught as a Data Source, but one that comes from the user as a Designer property itself.
In a later post, we will see that we actually hide this property when a Data Source is attached.
Now, let’s look at the bulk of the code, which is the onDataSourceValueChange event listener. This is invoked on notifyDataObservers
from the Observable Data components itself (we have seen this in the TinyDB), and the process is as follows:
First, checks are made to ensure that the calling component is the observed component (we compare memory addresses), and
the key is applicable to the Data Component (we use the null key as a convention for a global key)
Afterwards, an asynchronous operation is ran which handles data importing.
Initially, we check the old (current) Data Source Value. If the value is of type List, that means it most likely was
a properly formatted value. The removal is attempted via the Chart model (and invalid formats are handled in the model
itself, therefore we do not have to worry about badly formatted values here)
The new value is updated, and if the new value is of type List, the data is then imported.
The Chart is then refreshed after the process.
Note how we handle one of the most important considerations here – we update the values by removing the previous values contained in the
Data Series. For this reason, we keep track of what is termed as the current Data Source value, which we can then use for removal.
The OnDataSourceValueChange method in itself can use improvements (perhaps forcing List values, or generalizing this to support more
values), but it serves for the purpose of the currently supported Observable Chart Data Sources.
Lastly, we have the updateCururentDataSourceValue method which we have seen before in the TinyDB implementation.
This essentially updates the Data Source value on manual importing to ensure that the references are not broken
and up to date. This was especially an issue when manual data importing would happen before the Source is properly imported
(due to the initialize flag), therefore some measure was needed there. The method does all the necessary checks to ensure
that the component is indeed the observed one, and that the key value matches. Global key values are ignored (this might
be changed in the future)
CloudDB Importing
A component of similar nature is the CloudDB component, which is also an existing component in App Inventor
which stores key-value pairs as well, however, it connects to a Redis database rather than storing data locally,
therefore it is a bit more tricky to implement. However, most of the ideas remain the same as for the TinyDB
component.
Process
Since the majority of the process is roughly the same for the CloudDB component, we will mostly look at things that are different
compared to the TinyDB component.
Looking at the blocks, the public interface is, in fact, exactly the same:
The real differences become apparent in the implementation, and especially in the observation of the CloudDB component.
CloudDB implementation
Let’s first take a look at the CloudDB side of the implementation:
The first thing to note is the introduced getValueByTag method, which is essentially the logic of the previous
GetValueByTag, but moved to its own method to return a value. This is required to be able to effectively
reduce code redundancy with regards to retrieving the values (since this functionality is required in the
getDataValue method)
One of the most important changes done is in the DataChanged event method. Since the event gets invoked
whenever Data changes in the project, we can use the event to notify observers of data changes. The
only modification done in this method is the addition of the notifyDataObservers line, and this
allows us to make the CloudDB observable.
Afterwards, let’s look at the clearTag method. Since the DataChanged event is not invoked upon clearing
a Tag, the notification of the Data Observers with an empty value has to be done manually.
Finally, we have the getDataValue method, which, in fact, returns a Future object. Since
CloudDB can be performance heavy, we need to run the get operations in a background thread. Since
an ExecutorService is used in CloudDB to schedule tasks in order, we have to submit a task at the
end of the queue (after all of the data is changed effectively) with a new Callable, which holds
the result of the retrieved value. We return a Future object to be able to retrieve the value at a
later time (this helps avoid race conditions and ensures the correct data)
The logic in the Callable itself is quite similar to TinyDB – the value is retrieved by Tag,
and the value is then imported. There is an intermediate step for parsing the value from JSON,
since CloudDB stores its data in JSON.
With this implementation, we have effectively made the CloudDB component fully observable by
the Chart Data components.
Chart Data implementation
Since the majority of code for handling the observation of the CloudDB component is the same
as the TinyDB implementation, we will instead focus on a single method which imports
data from CloudDB:
What happens here, step by step, is as follows:
The two arguments (CloudDB and tag) are passed in to the method
The Future List object is retrieved from the CloudDB component using the tag
The data retrieval is ran asynchronously.
First, the listValue is retrieved from the Future object. This is a blocking call, but since we
are running asynchronously, this does not impact performance and helps ensure the correct value.
We update the current data source value (as with TinyDB)
The values are then imported via the Chart Data model, and the Chart is refreshed
Application examples
Let us now look at some concrete examples in App Inventor itself. We will analyze the same use
cases as with the TinyDB component.
Import from Blocks
Consider the following block setup:
The result when starting the app is as follows:
Observable Data Example
Consider the following properties and block setup:
The following animation illustrates how the observed data keeps changing:
Pull Requests
For the curious readers, the main pull requests related to these features can be found here:
This has been quite a lengthy post on the defined Data Source concepts and concrete TinyDB and CloudDB implementations.
Right now, as the post is being written, the project is nearing to an end. Due to the sheer amount of work, I could
not focus on blogging that much, but as the project is coming to a close, a series of blog posts are to follow documenting
all the other features.
Future blog posts will detail more Chart Data Source importing options, as well as newly introduced Chart types and smaller
features.
Overview
In the last post, I have thoroughly covered the implemented Pie Chart
type for the App Inventor Chart components project
that I have been working on...
Overview
Last post, I have wrapped up the implemented methods
for importing data to the Charts for the App Inventor Charts project
that I have been working o...
Overview
In the previous post on the App Inventor Chart Components project,
I have covered data importing from Files, as well as from the Web. Previously, we...
Overview
In the previous post on the App Inventor Chart Components project,
we have looked at an extensive description of Chart Data Source concepts and real...
Overview
It has been a while since the last post due to a highly busy period! A lot of blog posts were postponed, but now
a series of posts will follow in th...
Overview
Following up on the App Inventor Chart components project,
this blog post will focus on a specific workflow aspect that allows to select the Chart t...
Overview
With the workflow for the App Inventor Chart components established,
the next step is to define the concepts and format of the Data that can be impo...
Problem
While working with Charts in Android using the MPAndroidChart library, one inconsistency that I
stumbled upon is the fact that all the Charts support...
Overview
In the last post, I have previewed the workflow of the Charts components that I am working on
for App Inventor. In this post, I will dive into some ...
Overview
During the initial steps of the project to develop Chart Components for App Inventor, the major focus was on the design of the components
such that ...
As the community bonding period is nearing to an end and the coding period is about to begin, I would like give a status update on what
happened in the last ...
Introduction
I am Evaldas Latoškinas, currently a first year Computer Science & Engineering international student in the Netherlands at TU Delft.
Origina...