Blog

Backendless Data Collection Tutorial for Java/Android

by on September 10, 2019

Backendless Data Collection for Java and AndroidIn this article, we’ll consider some practical examples of how to use the Backendless Data Collection library to its fullest extent. You can find code documentation and sources for the library here.

This is an implementation of the standard Java Collection interface enabling you to retrieve, iterate, transform, perform computations over a collection of objects stored in a Backendless data table via Java collection.

Of course, that’s not all. It also has a number of features that can make your work with Backendless Data Service more convenient and seamless (from the point of working with collections). For example, it allows you to:

– iterate over all data in the table just as you would over an ordinary java collection;
– iterate over all data in the table without manual pagination;
– create a Java Iterator object to pass through the collection;
– create a Java Stream object and use advantages of Java stream API;
– add/remove collection operations to update the data in the table;
– use special persistence mode that allows you to work with previously received data offline;
– applicate the slice to limit the data that the collection will map to (such approach allows you to see and work with only the subset of objects).

Link library for the project

Before we start, you will need to link two libraries:

backendless library:

<dependency>
   <groupId>com.backendless</groupId>
   <artifactId>backendless</artifactId>
   <version>${bkndls.version}</version>
</dependency>

and backendless-data-collection library: Linked here.
(because it is not a part of the backendless SDK, you will need to download it manually)

<dependency>
   <groupId>com.backendless</groupId>
   <artifactId>backendless-data-collection</artifactId>
   <version>1.0.0</version>
   <scope>system</scope>
   <systemPath>${project.basedir}/lib/backendless-data-collection-1.0.jar</systemPath>
</dependency>

Preparation

1) For the examples, we’ll use a table with the following schema:

You may download and import the table schema with sample data from here.

Table name: Car
Fields:

brand STRING
model STRING
body STRING
color STRING
fuel_type STRING
engine_capacity INT
transmission STRING
issue_data DATETIME
condition STRING

 

2) Then you need to create Java classes for the data tables. Thanks to Code Generation, Backandless already does it for you. All you have to do is unpack the archive and add the Car.java class to your project.

Generate Java Code in Backendless

3) The last step that is required is to implement the interface

BackendlessDataCollection.Identifiable<>

The change is quite simple: just add an implements declaration for class and add method setObjectId.

public class Car implements BackendlessDataCollection.Identifiable<Car>
{
   @Override
   public String getObjectId() {
      return objectId;
   }

   @Override
   public void setObjectId( String s ) {
      objectId = s;
   }
}

Examples

Now we’re ready to start. Let’s see what we can do with the BackendlessDataCollection API.

Example 1

Create a simple collection that represents the entire dataset from our backend table.

BackendlessDataCollection<Car> cars = new BackendlessDataCollection<>( Car.class );

Get amount of records:

// doesn't make call to backend
int recordsCount = cars.size();

// invalidates state of this collection and retrieve the new size
cars.invalidateState();
cars.size();

Iterate over elements in collection (table) seamlessly without pagination (every iteration that reached the end (wasn’t interrupted) refreshes the actual collection size automatically, so you no need to call method invalidateState() if you regularly perform iteration):

for( Car car : cars )
{
   System.out.println( car.getBrand() + " :: " + car.getModel() );
}

// or shorter
cars.forEach( System.out::println );

Example 2

BackendlessDataCollection supports contains, add and remove operations. It also has a special method getById that is not present in an ordinary Java collection and gives you more capabilities to use.

// If you already know objectId of the entity
Car car = cars.getById( "600A5962-6686-3D62-FFCD-CCA5BAC2F800" );


// update entity and save it
car.setTransmission( "auto" );
car.setEngine_capacity( 2100 );
cars.add( car );


// Add new Car into collection
Car newCar = new Car();
newCar.setBrand( "BMW" );
newCar.setModel( "520M" );
newCar.setColor( "Wet Asphalt" );
newCar.setFuel_type( "diesel" );
newCar.setEngine_capacity( 2000 );
newCar.setCondition( "used" );
newCar.setTransmission( "auto" );
newCar.setIssue_date( Date.from( LocalDate.of( 2016, Month.MARCH, 10 ).atStartOfDay( ZoneId.systemDefault() ).toInstant()) );
boolean isSaved = cars.add( newCar );


// check the presence in the collection
// the logic takes into account only 'objectId'
Car myCar = new Car("600A5962-6686-3D62-FFCD-CCA5BAC2F800");
boolean myCarExists = cars.contains( myCar );

// you can use objects that you've gotten earlier from the collection
boolean newCarExists = cars.contains( newCar );

// check the presence of several objects
boolean newCarAndMyCarExist = cars.contains( Arrays.asList(myCar, newCar) );


// delete entity or multiple entities
boolean isRemoved = cars.remove( newCar );
isRemoved = cars.remove( Arrays.asList(myCar, newCar) );
cars.remove( new Car("<objectId>") );

// and also very useful method will retrieve all the data from remote table into local array
Car[] arrayOfCars = cars.toArray();

Example 3

Do you like Java data streams? They really make it easier to process objects. And BackendlessDataCollection supports all their features.

Set<String> brands = cars.stream()
   .map( Car::getBrand )
   .collect( Collectors.toSet() );


Date issueDate = Date.from( LocalDate.of( 2016, Month.MARCH, 10 ).atStartOfDay( ZoneId.systemDefault() ).toInstant());
List<Car> filteredCars = cars.stream()
   .filter( c -> "auto".equals( c.getTransmission() ) )
   .filter( c -> c.getIssue_date().after( issueDate ) )
   .sorted()
   .collect( Collectors.toList() );


Map<String, List<Car>> carsByBrand = cars.stream()
   .collect( Collectors.groupingBy(Car::getBrand) );

Example 4

What if you need to work only with a specific collection slice or, in other words, a subset of objects in your table?

// here we use the same table, but our collection will reflect
// only the particular subset of cars that have electric engine
cars = new BackendlessDataCollection<>( Car.class, "fuel_type = 'electro'" );

You keep all the power described in the previous parts, but now all methods are bordered with the query you put in the constructor.

So the iteration through such collection will take into account only cars with electric engine, and the size() method will return the number of cars only of such type.

contains(), add() and remove() operations directly perform calls to the Backendless server and would be discarded if the object doesn’t match the slice clause. I.e. if you try to add the object that has ‘gasoline’ engine, the new record won’t be created in the table. Or if you try to remove an object with a ‘diesel’ engine, the operation won’t be performed.

Example 5

The above examples cover the use cases where the number of objects is quite small or the data change very frequently.

Let’s dive deeper. Imagine that we have a large amount of data in our table. Thus the usage of Java streams can take a significant amount of time, network resources, and API calls.

BackendlessDataCollection gives you tools to solve such a problem — you can use persisted mode. Some operations would perform locally (without API calls to the server) and thus drastically reduce execution time.

Look closer at these opportunities.

Persisted mode is set during BackendlessDataCollection instance creation and cannot be changed further.

// set persisted mode
cars = new BackendlessDataCollection<>( Car.class, true );
// set persisted mode with the slice simultaneously
cars = new BackendlessDataCollection<>( Car.class, "fuel_type = 'electro'", true );

The collection is lazy, thus the data will be loaded only during iteration over it. So only the first iteration will perform calls to the Backendless server; all subsequent iterations will be done locally without any request. But you may force the refresh of the collection with the method invalidateState() which updates the real size and clears all locally saved data so the next iteration will make requests to the server again.

Method getPersistedSize() returns the size of locally saved data. It may be less or equal to size().

Method populate() forcibly downloads all data from the table (so-called greedy initialization). If isLoaded() == true, it does nothing.

If the iterator was interrupted, only part of the data would be saved locally and next time, the iterator will first iterate over local data and then (if needed) start making calls to the server.


We can’t wait to see what you build with this new tool.

Thanks for reading, and Happy Coding!