Blog

How to load related data objects – the ‘semi-lazy’ approach

by on February 4, 2015

In other articles, we’ve discussed how to load object relations using the auto-load and the one-step approach. Both of these approaches return a complex hierarchy of data where the parent object includes child entities at the time when it is retrieved from the server.

Quite often these approaches are less than desirable for the reason that they may result in large payloads of data which may slow down data transfer and result in a suboptimal user experience. Additionally, an entity stored in the Backendless database may have a lot of related properties and it may be desirable to fetch a specific relation after the parent object has been loaded by the client.

To enable loading of relations after the client has retrieved the parent object, Backendless provides API to load related objects with a two-step approach. The “two-step” term describes the fact that the parent entity is loaded with the first call and the second call/step is to fetch specific relations. The sample code below demonstrates retrieval of the “locations” property for the Restaurant objects. This example is based on the Restaurant-to-Go application schema.


Please follow the steps below to configure your Backendless backend with the schema and data for the application:

  1. Download the ZIP file which contains the definition for all tables from: https://backendless.com/documentation/samples/restaurant-app-schema.zip
  2. Login to Backendless Console, select an app (it is recommended to create a new app for this) and click the Manage icon.
  3. Select the Import menu item.
  4. Click the single Zip file link located in the description text and browse to the ZIP file from step 1 above.

The restaurant table the sample works with looks as shown below:

Notice the “Cantina Laredo” restaurant has related objects in the “locations” property. The code below retrieves a collection of the Restaurant objects (first step). Then the code makes a request to load the specific relation for each restaurant object:

    Asynchronous sample (Plain Java and Android):

    private static void loadRelationsAsync() throws Throwable
    {
        System.out.println( "\n============ Loading relations with the ASYNC API ============" );
        final CountDownLatch latch=new CountDownLatch( 1 );
        final AsyncCallback<Restaurant> loadRelationsCallback = new AsyncCallback<Restaurant>()
        {
            @Override
            public void handleResponse( Restaurant restaurant )
            {
                System.out.println( "\nRestaurant name = " + restaurant.getName() );
                printLocations( restaurant.getLocations() );
            }
            @Override
            public void handleFault( BackendlessFault backendlessFault )
            {
            }
        };
        AsyncCallback<BackendlessCollection<Restaurant>> callback=new AsyncCallback<BackendlessCollection<Restaurant>>()
        {
            @Override
            public void handleResponse( BackendlessCollection<Restaurant> restaurants )
            {
                System.out.println( "Loaded " + restaurants.getCurrentPage().size() + " restaurant objects" );
                System.out.println( "Total restaurants in the Backendless storage - " + restaurants.getTotalObjects() );
                Iterator<Restaurant> iterator=restaurants.getCurrentPage().iterator();
                while( iterator.hasNext() )
                {
                    Restaurant restaurant=iterator.next();
                    List<String> relations = new ArrayList<String>();
                    relations.add( "locations" );
                    Backendless.Data.of( Restaurant.class ).loadRelations( restaurant, relations, loadRelationsCallback );
                }
                latch.countDown();
            }
            @Override
            public void handleFault( BackendlessFault backendlessFault )
            {
            }
        };
        Backendless.Data.of( Restaurant.class ).find( callback );
        latch.await();
    }

    Synchronous sample (Plain Java):

    private static void loadRelationsSync() throws Exception
    {
        System.out.println( "\n============ Loading relations with the SYNC API ============" );
        BackendlessCollection<Restaurant> restaurants = Backendless.Data.of( Restaurant.class ).find();
        System.out.println( "Loaded " + restaurants.getCurrentPage().size() + " restaurant objects" );
        System.out.println( "Total restaurants in the Backendless storage - " + restaurants.getTotalObjects() );
        Iterator<Restaurant> iterator = restaurants.getCurrentPage().iterator();
        while (iterator.hasNext())
        {
            Restaurant restaurant = iterator.next();
            System.out.println( "\nRestaurant name = " + restaurant.getName() );
            List<String> relations = new ArrayList<String>();
            relations.add( "locations" );
            Backendless.Data.of( Restaurant.class ).loadRelations( restaurant, relations );
            printLocations( restaurant.getLocations() );
        }
    }

    The printLocations method used to print out the details of the related locations is:

    private static void printLocations( List<Location> locations )
    {
        if( locations == null )
        {
            System.out.println( "Restaurant locations have not been loaded" );
        }
        else if( locations.size() == 0 )
        {
            System.out.println( "There are no related locations" );
        }
        else
        {
            Iterator<Location> iterator = locations.iterator();
            while( iterator.hasNext() )
            {
                Location location = iterator.next();
                System.out.println( "Location: Street address - " + location.getStreetAddress() + ", City - " + location.getCity() );
            }
        }
    }

    The output produced by these methods:

    ============ Loading relations with the SYNC API ============
    Loaded 4 restaurant objects
    Total restaurants in the Backendless storage - 4
    Restaurant name = McDonald's
    There are no related locations
    Restaurant name = Buca Di Bepo
    There are no related locations
    Restaurant name = Cantina Laredo
    Location: Street address - 123 Main St., City - Frisco
    Restaurant name = Endless Sweets
    There are no related locations
    ============ Loading relations with the ASYNC API ============
    Loaded 4 restaurant objects
    Total restaurants in the Backendless storage - 4
    Restaurant name = McDonald's
    There are no related locations
    Restaurant name = Endless Sweets
    There are no related locations
    Restaurant name = Buca Di Bepo
    There are no related locations
    Restaurant name = Cantina Laredo
    Location: Street address - 123 Main St., City - Frisco

    //
    //  ViewController.m
    #import "ViewController.h"
    #import "Backendless.h"
    #import "Menu.h"
    #import "MenuItem.h"
    #import "Location.h"
    #import "Order.h"
    #import "Restaurant.h"
    #define TEST_SERVER 0
    static NSString *APP_ID = @"YOUR-APP-ID";
    static NSString *SECRET_KEY = @"YOUR-SECRET-KEY";
    static NSString *VERSION_NUM = @"v1";
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        [backendless initApp:APP_ID secret:SECRET_KEY version:VERSION_NUM];
        backendless.hostURL = @"http://api.backendless.com";
        [self twoStepsLoadRelationsSync];
        [self twoStepsLoadRelationsAsync];
    }
    #pragma mark -
    #pragma mark Private Methods
    #define PAGESIZE 100
    -(void)printLocations:(NSArray *)locations {
        if (!locations) {
            NSLog(@"Restaurant locations have not been loaded");
            return;
        }
        if (!locations.count) {
            NSLog(@"There are no related locations");
            return;
        }
        for (Location *location in locations) {
            NSLog(@"Location: Street address - %@, City - %@", location.streetAddress, location.city);
        }
    }
    -(void)twoStepsLoadRelationsSync {
        NSLog(@"\n============ Loading relations with the SYNC API ============");
        @try {
            BackendlessDataQuery *query = [BackendlessDataQuery query];
            BackendlessCollection *restaurants = [[backendless.persistenceService of:[Restaurant class]] find:query];
            NSArray *currentPage =[restaurants getCurrentPage];
            NSLog(@"Loaded %lu restaurant objects", (unsigned long)[currentPage count]);
            NSLog(@"Total restaurants in the Backendless storage - %@", [restaurants getTotalObjects]);
            for (Restaurant *restaurant in currentPage) {
                [backendless.persistenceService load:restaurant relations:@[@"locations"]];
                NSLog(@"Restaurant name = %@", restaurant.name);
                [self printLocations:restaurant.locations];
            }
        }
        @catch (Fault *fault) {
            NSLog(@"Server reported an error: %@", fault);
        }
    }
    -(void)twoStepsLoadRelationsAsync {
        NSLog(@"\n============ Loading relations with the ASYNC API ============");
        BackendlessDataQuery *query = [BackendlessDataQuery query];
        [[backendless.persistenceService of:[Restaurant class]]
         find:query
         response:^(BackendlessCollection *restaurants) {
             NSArray *currentPage =[restaurants getCurrentPage];
             NSLog(@"Loaded %lu restaurant objects", (unsigned long)[currentPage count]);
             NSLog(@"Total restaurants in the Backendless storage - %@", [restaurants getTotalObjects]);
             for (Restaurant *restaurant in currentPage) {
                 [backendless.persistenceService load:restaurant relations:@[@"locations"]
                  response:^(Restaurant *r) {
                      NSLog(@"Restaurant name = %@", r.name);
                      [self printLocations:r.locations];
                  }
                  error:^(Fault *fault) {
                      NSLog(@"Server reported an error: %@", fault);
                  }];
             }
         }
         error:^(Fault *fault) {
             NSLog(@"Server reported an error: %@", fault);
         }];
    }
    @end

    //
    //  ViewController.swift
    import UIKit
    class ViewController: UIViewController {
        let APP_ID = "YOUR-APP-ID"
        let SECRET_KEY = "YOUR-IOS-SECRET-KEY"
        let VERSION_NUM = "v1"
        let PAGESIZE = 100
        var backendless = Backendless.sharedInstance()
        override func viewDidLoad() {
            super.viewDidLoad()
            backendless.initApp(APP_ID, secret:SECRET_KEY, version:VERSION_NUM)
            twoStepsLoadRelationsSync()
            twoStepsLoadRelationsAsync()
        }
        func printLocations(locations: [Location]?) {
            if locations == nil {
                println("Restaurant locations have not been loaded")
                return
            }
            if locations?.count == 0 {
                println("There are no related locations")
                return
            }
            for location in locations! {
                println("Location: Street address - \(location.streetAdress), City - \(location.city)")
            }
        }
        func twoStepsLoadRelationsSync() {
            println("\n============ Loading relations with the SYNC API ============")
            Types.try({ () -> Void in
                var query = BackendlessDataQuery()
                var restaurants = self.backendless.persistenceService.of(Restaurant.ofClass()).find(query)
                var currentPage = restaurants.getCurrentPage()
                println("Loaded \(currentPage.count) restaurant objects")
                println("Total restaurants in the Backendless starage - \(restaurants.totalObjects)")
                for restaurant in currentPage {
                    self.backendless.persistenceService.load(restaurant, relations:["locations"])
                    println("Restaurant name = \(restaurant.name)")
                    self.printLocations(restaurant.locations as? [Location])
                }
                },
                catch: { (exception) -> Void in
                    println("Server reported an error: \(exception as Fault)")
                }
            )
        }
        func twoStepsLoadRelationsAsync() {
            println("\n============ Loading relations with the ASYNC API ============")
            var query = BackendlessDataQuery()
            backendless.persistenceService.of(Restaurant.ofClass()).find(
                query,
                response: { (var restaurants : BackendlessCollection!) -> () in
                    var currentPage = restaurants.getCurrentPage()
                    println("Loaded \(currentPage.count) restaurant objects")
                    println("Total restaurants in the Backendless starage - \(restaurants.totalObjects)")
                    for restaurant in currentPage {
                        self.backendless.persistenceService.load(
                            restaurant,
                            relations:["locations"],
                            response: { (var r : AnyObject!) -> () in
                                println("Restaurant name = \(r.name)")
                                self.printLocations(r.locations as? [Location])
                            },
                            error: { (var fault : Fault!) -> () in
                                println("Server reported an error: \(fault)")
                            }
                        )
                    }
                },
                error: { (var fault : Fault!) -> () in
                    println("Server reported an error: \(fault)")
                }
            )
        }
    }


    Asynchronous API sample:

    function rest(args){
      args = args || null;
    }
    function printLocations(locations){
      if(!locations){
        console.log( "Restaurant locations have not been loaded" );
      } else if(!locations.length){
        console.log( "There are no related locations" );
      } else {
        for(var i = 0; i < locations.length; i++){
          console.log( "Location: Street address - " + locations[i].address + ", City - " + locations[i].city );
        }
      }
    }
    console.log("\n============ Loading relations with the ASYNC API ============");
    function successCallback(restaurants){
      var restaurants = Backendless.Persistence.of(rest).find();
      console.log("Loaded (currentPage.count) restaurant objects " + restaurants.data.length);
      console.log("Total restaurants in the Backendless storage - " + restaurants.totalObjects);
      for (var i = 0, len = restaurants.data.length; i < len; i++) {
        console.log(restaurants.data[i]);
          Backendless.Persistence.of(rest).loadRelations( restaurants.data[i], ["locations"] );
          printLocations(restaurants.data[i].locations);
      }
    }
    function errorCallback(e){
      console.log(e);
    }
    var async = new Backendless.Async(successCallback, errorCallback);
    Backendless.Persistence.of(rest).find(async);

    Synchronous API sample:

    function rest(args){
      args = args || null;
    }
    function printLocations(locations){
      if(!locations){
        console.log( "Restaurant locations have not been loaded" );
      } else if(!locations.length){
        console.log( "There are no related locations" );
      } else {
        for(var i = 0; i < locations.length; i++){
          console.log( "Location: Street address - " + locations[i].address + ", City - " + locations[i].city );
        }
      }
    }
    console.log("\n============ Loading relations with the SYNC API ============");
    try {
      var restaurants = Backendless.Persistence.of(rest).find();
      console.log("Loaded (currentPage.count) restaurant objects " + restaurants.data.length);
      console.log("Total restaurants in the Backendless storage - " + restaurants.totalObjects);
      for (var i = 0, len = restaurants.data.length; i < len; i++) {
        console.log(restaurants.data[i]);
          Backendless.Persistence.of(rest).loadRelations( restaurants.data[i], ["locations"] );
          printLocations(restaurants.data[i].locations);
      }
    }
    catch(e){
      console.log(e);
    }


    See the documentation for more information about loading relations with the two-step approach.

    Enjoy!