We use essential cookies for the website to function, as well as analytics cookies for analyzing and creating statistics of the website performance. To agree to the use of analytics cookies, click "Accept All". You can manage your preferences at any time by clicking "Cookie Settings" on the footer. More Information.

Only Essential Cookies
Accept All

Other Operations Supported by Cloud DB

Mapping Object Types

Cloud DB provides the capability of mapping object type files. When developing a local app integrated with the SDK for Android, you can directly add the object type file in Java format exported from AppGallery Connect to the local development environment. If the object type file contains object type names (class names) or field names (attribute names) that do not comply with the Java specifications, you can modify the object type names and field names, add annotations to establish object type mappings between the cloud and local development environment, and use the mapped object type names and field names to meet the language specifications. In object type mappings, ObjectTypeMapping maps classes (object types) and FieldMapping maps attributes (fields) in Java files.

Precautions:

NOTICE

Read the precautions carefully.

  • The source and target object type names must be different in a mapping. Otherwise, an exception will occur when the createObjectType() method is called to create or modify the object type.
  • Object type names are case sensitive in mappings. The source ones must be the same as those in AppGallery Connect.
  • The source field names must be the same as those in AppGallery Connect.
  • If a successfully mapped field is an input parameter, use the new field name for input.
  • When an object type is mapped, field names in the index and primary key annotations cannot be modified. They must be the same as the object type field names created in AppGallery Connect.

Procedure:

  1. Add all Java files exported from AppGallery Connect to the local development environment.

    If any file already exists, replace it.

  2. Open the book_info.java file that does not comply with the naming rules. The following is an example:
    @PrimaryKeys({"id"})
    public final class book_info extends CloudDBZoneObject {
        private Long id;
        private Date publish_time;
    
        public book_info() {
            super(book_info.class);
        }
        
        public void setId(Long id) {
            this.id= id;
        }
    
        public String getId() {
            return id;
        }
    
        public void setPublish_time(Date publish_time) {
            this.publish_time = publish_time
        }
    
        public String getPublish_time() {
            return publish_time;
        }
    }
  3. Map the object type file. After the mapping is successful, modify all content that uses the object type name and field name in the project.
    1. Map the object type name. Add the @ObjectTypeMapping annotation for the class name to map the object type BookInfo to book_info and modify the corresponding constructor and file name.
      @PrimaryKeys({"id"})
      @ObjectTypeMapping("book_info")
      public final class BookInfo extends CloudDBZoneObject {
          private Long id;
          private Date publish_time;
      
          public BookInfo() {
              super(BookInfo.class);
          }
          
          public void setId(Long id) {
              this.id= id;
          }
      
          public String getId() {
              return id;
          }
      
          public void setPublish_time(Date publish_time) {
              this.publish_time = publish_time
          }
      
          public String getPublish_time() {
              return publish_time;
          }
      }
    2. Map the field name. Add the @FieldMapping annotation for the field name to map the field publish_time to publishTime.
      @PrimaryKeys({"id"})
      @ObjectTypeMapping("book_info")
      public final class BookInfo extends CloudDBZoneObject {
          private Long id;
          @FieldMapping("publish_time")
          private Date publishTime;
      
          public BookInfo() {
              super(BookInfo.class);
          }
          
          public void setId(Long id) {
              this.id= id;
          }
      
          public String getId() {
              return id;
          }
      
          public void setPublishTime(Date publishTime) {
              this.publishTime = publishTime
          }
      
          public String getPublishTime() {
              return publishTime;
          }
      }
  4. Initialize Cloud DB. Use the createObjectType() method in the AGConnectCloudDB class to define and create object types.

Writing Data

You can use executeUpsert() to write one object or a group of objects to the current Cloud DB zone. If an object with the same primary key exists in the Cloud DB zone, the existing object will be updated. Otherwise, a new object is written. The written data can be obtained from a new object or an object queried from the Cloud DB zone.

When you write a group of objects, the write operation is atomic. That is, all objects in the object list either are successfully written or fail to be written.

After the data write method is called, a Task object is returned. The object encapsulates the status, result, and exception information of the write operation. The Task object can be used to asynchronously listen to the execution result. If the execution succeeds, you can obtain the number of written objects. Otherwise, you can obtain the corresponding exception information. For details, please refer to Tasks.

Precautions:

NOTICE

Read the precautions carefully.

  • You can write objects only when the Cloud DB zone is opened. Otherwise, the write operation will fail.
  • The objects to be written in a batch must belong to the same object type. Otherwise, the write operation will fail.

Sample code:

Write the BookInfo object into the Cloud DB zone. If the write operation succeeds, the number of written data records is returned. Otherwise, an exception will be thrown.

Java
Kotlin
public void upsertBookInfos(BookInfo bookInfo) {
	if (mCloudDBZone == null) {
		Log.w(TAG, "CloudDBZone is null, try re-open it");
		return;
	}
	Task<Integer> upsertTask = mCloudDBZone.executeUpsert(bookInfo);
	upsertTask.addOnSuccessListener(new OnSuccessListener<Integer>() {
		@Override
		public void onSuccess(Integer cloudDBZoneResult) {
			Log.i(TAG, "Upsert " + cloudDBZoneResult + " records");
		}
	}).addOnFailureListener(new OnFailureListener() {
		@Override
		public void onFailure(Exception e) {
			mUiCallBack.updateUiOnError("Upsert book info failed");
		}
	});
}
fun upsertBookInfo(bookInfo: BookInfo?) {
    if (mCloudDBZone == null) {
        Log.w(TAG, "CloudDBZone is null, try re-open it")
        return
    }
    val upsertTask = mCloudDBZone!!.executeUpsert(bookInfo!!)
    upsertTask.addOnSuccessListener { cloudDBZoneResult ->
        Log.i(TAG, "Upsert $cloudDBZoneResult records")
    }.addOnFailureListener {
        mUiCallBack.updateUiOnError("Upsert book info failed")
    }
}

Querying Data

Cloud DB uses the executeQuery() method to query objects and provides various query predicates, such as equalTo(), notEqualTo(), and in(). By using one or more chain query conditions, you can query objects that meet the specific query conditions from a Cloud DB zone, or sort query results or limit the number of returned query results by using the corresponding predicates. For details about the query conditions, please refer to CloudDBZoneQuery.

There are two Cloud DB data synchronization modes: cache mode and local mode. The data query policy varies according to the data synchronization mode. The data sources are as follows in different modes:

  • Cache mode
    • Local cache
    • Cloud DB zone on the cloud
    • Both Cloud DB zone on the cloud and local cache
  • Local mode
    • Cloud DB zone on the device

Call the getResult() method of the Task<TResult> class to obtain the CloudDBZoneSnapshot object that is stored in the returned objects. When obtaining the object, you can also use some auxiliary methods to perform different operations as needed. For example, use the subscribeSnapshot() method to add a listener for changes of the query result set, use the hasPendingWrites() method to determine whether there are objects that are not synchronized to the cloud, and use the isFromCloud() method to determine the object source in the query results. For details, please refer to CloudDBZoneSnapshot.

In addition to basic query, Cloud DB supports querying the average value of a field and objects in the local cache that are not synchronized to Cloud DB.

Precautions:

NOTICE

Read the precautions carefully.

  • You can query objects only when the Cloud DB zone is opened. Otherwise, the query operation will fail.
  • You can query the data of only one object type at a time.

Simple Query

You can obtain all objects of an object type when no query conditions are set. You can also specify a query condition to obtain desired objects.

  • Query all data of the BookInfo object type.
    Java
    Kotlin
    public void queryAllBooks() {
    	if (mCloudDBZone == null) {
    		Log.w(TAG, "CloudDBZone is null, try re-open it");
    		return;
    	}
    	Task<CloudDBZoneSnapshot<BookInfo>> queryTask = mCloudDBZone.executeQuery(
    		CloudDBZoneQuery.where(BookInfo.class),
    		CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
    	queryTask.addOnSuccessListener(new OnSuccessListener<CloudDBZoneSnapshot<BookInfo>>() {
    		@Override
    		public void onSuccess(CloudDBZoneSnapshot<BookInfo> snapshot) {
    			processQueryResult(snapshot);
    		}
    	}).addOnFailureListener(new OnFailureListener() {
    		@Override
    		public void onFailure(Exception e) {
    			mUiCallBack.updateUiOnError("Query book list from cloud failed");
    		}
    	});
    }
    fun queryAllBooks() {
        if (mCloudDBZone == null) {
            Log.w(TAG, "CloudDBZone is null, try re-open it")
            return
        }
        val queryTask = mCloudDBZone!!.executeQuery(
                CloudDBZoneQuery.where(BookInfo::class.java),
                CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY)
        queryTask.addOnSuccessListener {snapshot -> processQueryResult(snapshot) }
                .addOnFailureListener {
                    mUiCallBack.updateUiOnError("Query book list from cloud failed")
                }
    }
  • Asynchronously listen to books whose bookName is A Tale of Two Cities.
    • Construct the queryBooks method, which can be referenced by subsequent queries.
      Java
      Kotlin
      public void queryBooks(CloudDBZoneQuery<BookInfo> query) {
      	if (mCloudDBZone == null) {
      		Log.w(TAG, "CloudDBZone is null, try re-open it");
      		return;
      	}
      	Task<CloudDBZoneSnapshot<BookInfo>> queryTask = mCloudDBZone.executeQuery(query,
      		CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
      	queryTask.addOnSuccessListener(new OnSuccessListener<CloudDBZoneSnapshot<BookInfo>>() {
      		@Override
      		public void onSuccess(CloudDBZoneSnapshot<BookInfo> snapshot) {
      			processQueryResult(snapshot);
      		}
      	}).addOnFailureListener(new OnFailureListener() {
      		@Override
      		public void onFailure(Exception e) {
      			mUiCallBack.updateUiOnError("Query failed");
      		}
      	});
      }
      
      private void processQueryResult(CloudDBZoneSnapshot<BookInfo> snapshot) {
      	CloudDBZoneObjectList<BookInfo> bookInfoCursor = snapshot.getSnapshotObjects();
      	List<BookInfo> bookInfoList = new ArrayList<>();
      	try {
      		while (bookInfoCursor.hasNext()) {
      			BookInfo bookInfo = bookInfoCursor.next();
      			bookInfoList.add(bookInfo);
      		}
      	} catch (AGConnectCloudDBException e) {
      		Log.w(TAG, "processQueryResult: " + e.getMessage());
      	}
      	snapshot.release();
      	mUiCallBack.onAddOrQuery(bookInfoList);
      }
      fun queryBooks(query: CloudDBZoneQuery<BookInfo>) {
          if (mCloudDBZone == null) {
              Log.w(TAG, "CloudDBZone is null, try re-open it")
              return
          }
      
          val queryTask = mCloudDBZone!!.executeQuery(query,
                  CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY)
          queryTask.addOnSuccessListener { snapshot -> processQueryResult(snapshot) }
              .addOnFailureListener { mUiCallBack.updateUiOnError("Query failed")}
      }
      
      private fun processQueryResult(snapshot: CloudDBZoneSnapshot<BookInfo>) {
          val bookInfoCursor = snapshot.snapshotObjects
          val bookInfoList: MutableList<BookInfo> = ArrayList()
          try {
              while (bookInfoCursor.hasNext()) {
                  val bookInfo = bookInfoCursor.next()
                  bookInfoList.add(bookInfo)
              }
          } catch (e: AGConnectCloudDBException) {
              Log.w(TAG, "processQueryResult: " + e.message)
          } finally {
              snapshot.release()
          }
          mUiCallBack.onAddOrQuery(bookInfoList)
      }
    • Construct query conditions and call the queryBooks method to query books.
      Java
      Kotlin
      CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class).equalTo(BookEditFields.BOOK_NAME, "A Tale of Two Cities");
      queryBooks(query);
      val query = CloudDBZoneQuery.where(BookInfo::class.java).equalTo(BookEditFields.BOOK_NAME, "A Tale of Two Cities")
      queryBooks(query)

Compound Query

You can use multiple chain query conditions to obtain desired objects. By default, multiple chain conditions are connected using the AND operation, that is, and().

  • Construct query conditions: bookName contains Database and price is higher than 20.0 and lower than 50.0. Then call the queryBooks method to query books.
    Java
    Kotlin
    CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class).contains(BookEditFields.BOOK_NAME, 
    	"Database").greaterThan(BookEditFields.PRICE, 20.0).and().lessThan(BookEditFields.PRICE, 50.0);
    queryBooks(query);
    val query = CloudDBZoneQuery.where(BookInfo::class.java).contains(BookEditFields.BOOK_NAME,
            "Database").greaterThan(BookEditFields.PRICE, 20.0).and().lessThan(BookEditFields.PRICE, 50.0)
    queryBooks(query)
  • Construct query conditions: bookName contains Database and price is lower than 20.0 or higher than 50.0. Then call the queryBooks method to query books.
    Java
    Kotlin
    CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class).contains(BookEditFields.BOOK_NAME,
    	"Database").lessThan(BookEditFields.PRICE, 20.0).or().greaterThan(BookEditFields.PRICE, 50.0);
    queryBooks(query);
    val query = CloudDBZoneQuery.where(BookInfo::class.java).contains(BookEditFields.BOOK_NAME,
            "Database").lessThan(BookEditFields.PRICE, 20.0).or().greaterThan(BookEditFields.PRICE, 50.0)
    queryBooks(query)
  • Construct query conditions: bookName contains A Tale of Two Cities, author is Charles Dickens, and price is higher than 60.0. Then call the queryBooks method to query books.
    Java
    Kotlin
    CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class).contains(BookEditFields.BOOK_NAME,
    	"A Tale of Two Cities").equalTo(BookEditFields.AUTHOR, "Charles Dickens").greaterThan(BookEditFields.PRICE, 60.0);
    queryBooks(query);
    val query = CloudDBZoneQuery.where(BookInfo::class.java).contains(BookEditFields.BOOK_NAME,
            "A Tale of Two Cities").equalTo(BookEditFields.AUTHOR, "Charles Dickens").greaterThan(BookEditFields.PRICE, 60.0)
    queryBooks(query)
  • Construct query conditions: bookName contains Autobiography, author is William Shakespeare or Charles Dickens, and price is higher than 60.0. Then call the queryBooks method to query books.
    Java
    Kotlin
    CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class).contains(BookEditFields.BOOK_NAME,
    	"Autobiography").beginGroup().equalTo(BookEditFields.AUTHOR, "William Shakespeare").or().equalTo(BookEditFields.AUTHOR, "Charles Dickens").endGroup().greaterThan(BookEditFields.PRICE, 60.0);
    queryBooks(query);
    val query = CloudDBZoneQuery.where(BookInfo::class.java).contains(BookEditFields.BOOK_NAME,
            "Autobiography").beginGroup().equalTo(BookEditFields.AUTHOR, "William Shakespeare").or().equalTo(BookEditFields.AUTHOR, "Charles Dickens").endGroup().greaterThan(BookEditFields.PRICE, 60.0);
    queryBooks(query)

Aggregate Query

When querying data, you can use aggregate queries to query the average value, sum, maximum value, minimum value, and number of statistics records.

Querying the Average Value

You can call executeAverageQuery() to query objects that meet a specific query condition and calculate the average value of a specified field in the objects.

Query the average price of all books.
Java
Kotlin
CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class);
Task<Double> averageQueryTask = mCloudDBZone.executeAverageQuery(query, BookEditFields.PRICE,
	CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
averageQueryTask.addOnSuccessListener(new OnSuccessListener<Double>() {
	@Override
	public void onSuccess(Double cloudDBZoneResult) {
		Log.i(TAG, "Average price is " + cloudDBZoneResult);
	}
}).addOnFailureListener(new OnFailureListener() {
	@Override
	public void onFailure(Exception ex) {
		Log.w(TAG, "Average query is failed: " + Log.getStackTraceString(ex));
	}
});
val query = CloudDBZoneQuery.where(BookInfo::class.java)
val averageQueryTask = mCloudDBZone!!.executeAverageQuery(query, BookEditFields.PRICE,
        CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY)
averageQueryTask.addOnSuccessListener {cloudDBZoneResult ->
    Log.i(TAG, "Average price is $cloudDBZoneResult")
}.addOnFailureListener {
    Log.w(TAG, "Average query is failed: " + Log.getStackTraceString(it))
}

Querying the Sum

You can use executeSumQuery() to search for eligible objects and return the sum of the data record values of the specified fields.

Query the total price of all books.
Java
Kotlin
CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class);
Task<Number> sumQueryTask = mCloudDBZone.executeSumQuery(query, BookEditFields.PRICE,
	CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
sumQueryTask.addOnSuccessListener(new OnSuccessListener<Number>() {
	@Override
	public void onSuccess(Number number) {
		Log.i(TAG, "The sum of prices is " + number);
	}
}).addOnFailureListener(new OnFailureListener() {
	@Override
	public void onFailure(Exception e) {
		Log.w(TAG, "Sum query is failed: " + Log.getStackTraceString(e));
	}
});
val query = CloudDBZoneQuery.where(BookInfo::class.java)
val sumQueryTask = mCloudDBZone!!.executeSumQuery(query, BookEditFields.PRICE,
        CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY)
sumQueryTask.addOnSuccessListener { number ->
    Log.i(TAG, "The sum of prices is $number")
}.addOnFailureListener {
    Log.w(TAG, "Sum query is failed: " + Log.getStackTraceString(it))
}

Querying the Maximum and Minimum Values

You can use executeMaximumQuery() or executeMinimalQuery() to search for eligible objects and obtain the maximum or minimum value in the data record values of the specified fields.

  • Query the highest price among all books.
    Java
    Kotlin
    CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class);
    Task<Number> maxQueryTask = mCloudDBZone.executeMaximumQuery(query, BookEditFields.PRICE,
    	CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
    maxQueryTask.addOnSuccessListener(new OnSuccessListener<Number>() {
    	@Override
    	public void onSuccess(Number number) {
    		Log.i(TAG, "The most expensive price is " + number);
    	}
    }).addOnFailureListener(new OnFailureListener() {
    	@Override
    	public void onFailure(Exception e) {
    		Log.w(TAG, "Maximum query is failed: " + Log.getStackTraceString(e));
    	}
    });
    val query = CloudDBZoneQuery.where(BookInfo::class.java)
    val maxQueryTask = mCloudDBZone!!.executeMaximumQuery(query, BookEditFields.PRICE,
            CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY)
    maxQueryTask.addOnSuccessListener { number ->
        Log.i(TAG, "The most expensive price is $number")
    }.addOnFailureListener {
        Log.w(TAG, "Maximum query is failed: " + Log.getStackTraceString(it))
    }
  • Query the lowest price among all books.
    Java
    Kotlin
    CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class);
    Task<Number> minQueryTask = mCloudDBZone.executeMinimalQuery(query, BookEditFields.PRICE,
    	CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
    minQueryTask.addOnSuccessListener(new OnSuccessListener<Number>() {
    	@Override
    	public void onSuccess(Number number) {
    		Log.i(TAG, "The cheapest price is " + number);
    	}
    }).addOnFailureListener(new OnFailureListener() {
    	@Override
    	public void onFailure(Exception e) {
    		Log.w(TAG, "Minimal query is failed: " + Log.getStackTraceString(e));
    	}
    });
    val query = CloudDBZoneQuery.where(BookInfo::class.java)
    val minQueryTask = mCloudDBZone!!.executeMinimalQuery(query, BookEditFields.PRICE,
            CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY)
    minQueryTask.addOnSuccessListener { number ->
        Log.i(TAG, "The cheapest price is $number")
    }.addOnFailureListener {
        Log.w(TAG, "Minimal query is failed: " + Log.getStackTraceString(it))
    }

Querying the Number of Data Records

You can use executeCountQuery() to search for eligible objects and obtain the number of data records of the specified fields. During statistics collection, if the value of a field is not empty, count 1. Otherwise, count 0.

Query the number of books.
Java
Kotlin
CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class);
Task<Long> countQueryTask = mCloudDBZone.executeCountQuery(query, BookEditFields.BOOK_ID,
	CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
countQueryTask.addOnSuccessListener(new OnSuccessListener<Long>() {
	@Override
	public void onSuccess(Long aLong) {
		Log.i(TAG, "The total number of books is " + aLong);
	}
}).addOnFailureListener(new OnFailureListener() {
	@Override
	public void onFailure(Exception e) {
		Log.w(TAG, "Count query is failed: " + Log.getStackTraceString(e));
	}
});
val query = CloudDBZoneQuery.where(BookInfo::class.java)
val countQueryTask = mCloudDBZone!!.executeCountQuery(query, BookEditFields.BOOK_ID,
        CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY)
countQueryTask.addOnSuccessListener { aLong ->
    Log.i(TAG, "The total number of books is $aLong")
}.addOnFailureListener {
    Log.w(TAG, "Count query is failed: " + Log.getStackTraceString(it))
}

Sorting Data

You can use orderByAsc() or orderByDesc() to sort objects in the query result set by field in ascending or descending order.

When using orderByAsc() or orderByDesc() to sort fields, you are advised to create indexes for the sorted fields. Otherwise, when the number of data records of the object type reaches a certain value, the query may time out or fail due to low query efficiency, affecting user experience. For details about how to create an index, please refer to Adding and Exporting the Object Type File. After creating an index, you need to update the local object type file.

When sorting objects in query results, the sorting predicate must be placed after other query predicates and before the predicate that limits the number of returned query results.

Construct query conditions, call the queryBooks method to query books, and sort the books in descending order of price. Here, books cheaper than 50.0 are queried.

Java
Kotlin
CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class);
query.lessThan(BookEditFields.PRICE, 50.0).orderByDesc(BookEditFields.PRICE);
queryBooks(query);
val query = CloudDBZoneQuery.where(BookInfo::class.java)
query.lessThan(BookEditFields.PRICE, 50.0).orderByDesc(BookEditFields.PRICE)
queryBooks(query)

Querying Tuples

You can use the startAt(), startAfter(), endAt(), or endBefore() method to specify the start or end position for data query.

Obtaining a Group of Data Records After a Specified Record

Call the startAt() method to define the start position of the query. When the startAt() method is used separately, the system sorts data in ascending order of the primary key by default and returns data records following a specified record. The returned result includes the specified data record at the start position.
Java
Kotlin
BookInfo book = new BookInfo();
book.setId(5);
CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class).startAt(book);
queryBooks(query);
val book = BookInfo()
book.id = 5
val query = CloudDBZoneQuery.where(BookInfo::class.java).startAt(book)
queryBooks(query)

Performing Intersection Processing on the Queried Data

Call the startAt() method to define the start position of the query and call the lessThan() method to define the query conditions.
Java
Kotlin
// Query books whose BOOK_ID value range is [5,10).
BookInfo book = new BookInfo();
book.setId(5);
CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class)
	.lessThan(BookEditFields.BOOK_ID, 10)
	.orderByAsc(BookEditFields.BOOK_ID)
	.startAt(book);
queryBooks(query);
// Query books whose BOOK_ID value range is [5,10).
val book = BookInfo()
book.id = 5
val query = CloudDBZoneQuery.where(BookInfo::class.java)
        .lessThan(BookEditFields.BOOK_ID, 10)
        .orderByAsc(BookEditFields.BOOK_ID)
        .startAt(book);
queryBooks(query);

Obtaining Data Records Before a Specified Record in Query Results Sorted by Multiple Fields

Call endAt() and orderByAsc() to sort the queried data by multiple specified fields and return the data before the specified end position. The returned result contains the specified data record at the end position.

Java
Kotlin
// Query the books whose BookName is The Red and the Black and Price is 10.0 until the record with BOOK_ID set to 5 is found.
BookInfo book = new BookInfo();
book.setId(5);
book.setBookName("The Red and the Black");
book.setPrice(10.0);
CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class)
	.orderByAsc(BookEditFields.BOOK_NAME)
	.orderByAsc(BookEditFields.PRICE)
	.endAt(book);
queryBooks(query);
// Query the books whose BookName is The Red and the Black and Price is 10.0 until the record with BOOK_ID set to 5 is found.
val book = BookInfo()
book.id = 5
book.bookName = "The Red and the Black"
book.price = 10.0
val query = CloudDBZoneQuery.where(BookInfo::class.java)        
        .orderByAsc(BookEditFields.BOOK_NAME)
        .orderByAsc(BookEditFields.PRICE)
        .endAt(book)
queryBooks(query)

Pagination Query

You can use the limit() method separately or together with tuple query to implement pagination query. You can also call equalTo(), orderByAsc(), orderByDesc(), greaterThan(), greaterThanOrEqualTo(), lessThan(), lessThanOrEqualTo(), or(), beginGroup(), and endGroup() to construct query conditions.

To ensure the performance of pagination query, you need to create the index required by Cloud DB before constructing the pagination query conditions. If no index is created, you may receive an exception message. You need to create the missing index based on the exception message. For details about how to create an index, please refer to Adding and Exporting the Object Type File. After creating an index, you need to update the local object type file.

Using Tuple Query and the limit() Method Together

Call the startAt() method to define the start position of the query and call limit() and orderByAsc() to implement pagination. The last record in the first query result is used as the start point of the next query.

Java
Kotlin
// Query 10 records on the first page.
CloudDBZoneQuery<BookInfo> queryFirst = CloudDBZoneQuery.where(BookInfo.class)
        .equalTo(BookEditFields.PUBLISHER, "People's Press")
	.orderByAsc(BookEditFields.BOOK_ID)
	.limit(10);
Task<CloudDBZoneSnapshot<BookInfo>> queryTask = zone.executeQuery(queryFirst,
	CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_DEFAULT);

// Wait for the query result.
CloudDBZoneSnapshot<BookInfo> result = null;
queryTask.addOnSuccessListener(new OnSuccessListener<CloudDBZoneSnapshot<BookInfo>>() {
	@Override
	public void onSuccess(CloudDBZoneSnapshot<BookInfo> snapshot) {
		// Use the 10th record as the start point of the next query to obtain data records (excluding the record at the start point) on the next page.
		try {
			BookInfo nextBook = snapshot.getSnapshotObjects().get(snapshot.getSnapshotObjects().size() - 1);
			CloudDBZoneQuery<BookInfo> queryNext = CloudDBZoneQuery.where(BookInfo.class)
                                .equalTo(BookEditFields.PUBLISHER, "People's Press")
                                .startAfter(nextBook)            
				.orderByAsc(BookEditFields.BOOK_ID)
				.limit(10);
			Task<CloudDBZoneSnapshot<BookInfo>> queryTaskNext = zone.executeQuery(
				queryNext,
				CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_DEFAULT);
		} catch (AGConnectCloudDBException e) {
			Log.w(TAG, "Exception occurred, info:" + e.getMessage());
		} finally {
			snapshot.release();
		}
	}
}).addOnFailureListener(new OnFailureListener() {
	@Override
	public void onFailure(Exception e) {
		Log.e(TAG, "Query failed, info:" + Log.getStackTracesString(e));
	}
});
// Query 10 records on the first page.
val queryFirst = CloudDBZoneQuery.where(BookInfo::class.java)
        .equalTo(BookEditFields.PUBLISHER, "People's Press")
        .orderByAsc(BookEditFields.BOOK_ID)
        .limit(10);
val queryTask = mCloudDBZone!!.executeQuery(queryFirst,
        CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_DEFAULT)

// Wait for the query result.
queryTask.addOnSuccessListener { snapshot ->
    // Use the 10th record as the start point of the next query to obtain data records (excluding the record at the start point) on the next page.
    try {
        val nextBook = snapshot.snapshotObjects.get(snapshot.snapshotObjects.size() - 1)
        val queryNext = CloudDBZoneQuery.where(BookInfo::class.java)
            .equalTo(BookEditFields.PUBLISHER, "People's Press")
            .startAfter(nextBook)
            .orderByAsc(BookEditFields.BOOK_ID)
            .limit(10)
        val queryTaskNext = mCloudDBZone!!.executeQuery(queryNext,
            CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_DEFAULT)
    } catch (e: AGConnectCloudDBException) {
        Log.w(TAG, "Exception occurred, info:" + e.message)
    } finally {
        snapshot.release()
    }
}.addOnFailureListener { e ->
    Log.e(TAG, "Query failed, info:" + Log.getStackTracesString(e))
}

Using the limit Method Separately

Calculate the offset parameter of pagination data and use the limit method to implement pagination query.
Java
Kotlin
// Query the first 10 books cheaper than 50.
int pageNo = 1;    // Page number.
int pageSize = 10  // Total number of records on each page.
CloudDBZoneQuery<BookInfo> queryFirst = CloudDBZoneQuery.where(BookInfo.class)
        .lessThan(BookEditFields.PRICE, 50.0)
	.orderByAsc(BookEditFields.BOOK_ID)
	.limit(pageSize, (pageNo - 1) * pageSize);
Task<CloudDBZoneSnapshot<BookInfo>> queryTask = zone.executeQuery(queryFirst,
	CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_DEFAULT);

// Wait for the query result.
CloudDBZoneSnapshot<BookInfo> result = null;
queryTask.addOnSuccessListener(new OnSuccessListener<CloudDBZoneSnapshot<BookInfo>>() {
	@Override
	public void onSuccess(CloudDBZoneSnapshot<BookInfo> snapshot) {
		// Use the 10th record as the start point of the next query to obtain data records (excluding the record at the start point) on the next page.
                pageNo++;
		try {
			BookInfo nextBook = snapshot.getSnapshotObjects().get(snapshot.getSnapshotObjects().size() - 1);
			CloudDBZoneQuery<BookInfo> queryNext = CloudDBZoneQuery.where(BookInfo.class)
                            .lessThan(BookEditFields.PRICE, 50.0)
	                    .orderByAsc(BookEditFields.BOOK_ID)
	                    .limit(pageSize, (pageNo - 1) * pageSize);
			Task<CloudDBZoneSnapshot<BookInfo>> queryTaskNext = zone.executeQuery(
				queryNext,
				CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_DEFAULT);
		} catch (AGConnectCloudDBException e) {
			Log.w(TAG, "Exception occurred, info:" + e.getMessage());
		} finally {
			snapshot.release();
		}
	}
}).addOnFailureListener(new OnFailureListener() {
	@Override
	public void onFailure(Exception e) {
		Log.e(TAG, "Query failed, info:" + Log.getStackTracesString(e));
	}
});
// Query the first 10 books cheaper than 50.
val pageNo = 1;    // Page number.
val pageSize = 10  // Total number of records on each page.
val queryFirst = CloudDBZoneQuery.where(BookInfo::class.java)
        .lessThan(BookEditFields.PRICE, 50)
        .orderByAsc(BookEditFields.BOOK_ID)
        .limit(pageSize, (pageNo - 1) * pageSize)
val queryTask = mCloudDBZone!!.executeQuery(queryFirst,
        CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_DEFAULT)

// Wait for the query result.
queryTask.addOnSuccessListener { snapshot ->
    // Use the 10th record as the start point of the next query to obtain data records (excluding the record at the start point) on the next page.
    pageNo++
    try {
        val nextBook = snapshot.snapshotObjects.get(snapshot.snapshotObjects.size() - 1)
        val queryNext = CloudDBZoneQuery.where(BookInfo::class.java)
            .lessThan(BookEditFields.PRICE, 50)
            .orderByAsc(BookEditFields.BOOK_ID)
            .limit(pageSize, (pageNo - 1) * pageSize)
        val queryTaskNext = mCloudDBZone!!.executeQuery(queryNext,
            CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_DEFAULT)
    } catch (e: AGConnectCloudDBException) {
        Log.w(TAG, "Exception occurred, info:" + e.message)
    } finally {
        snapshot.release()
    }
}.addOnFailureListener { e ->
    Log.e(TAG, "Query failed, info:" + Log.getStackTracesString(e))
}

Deleting Data

You can use the executeDelete() method to delete a single object or a group of objects. When deleting data, Cloud DB will delete the corresponding data based on the input object primary key and does not check whether other attributes of the object are consistent with the stored data. The delete operation is atomic. That is, all objects in the list either are deleted successfully or fail to be deleted.

Precautions:

NOTICE

Read the precautions carefully.

  • You can delete objects only when the Cloud DB zone is opened. Otherwise, the deletion will fail.
  • Objects to be deleted in a batch must belong to the same object type. Otherwise, the deletion will fail.

Sample code:

When you delete objects, the number of deleted objects will be returned if the deletion succeeds; otherwise, an exception will be returned.

Java
Kotlin
// bookInfoList is the dataset returned by the query operation or listener.
public void deleteBookInfosAsync(List<BookInfo> bookInfoList) {
	if (mCloudDBZone == null) {
		Log.w(TAG, "CloudDBZone is null, try re-open it");
		return;
	}

	Task<Integer> deleteTask = mCloudDBZone.executeDelete(bookInfoList);
	deleteTask.addOnSuccessListener(new OnSuccessListener<Integer>() {
		@Override
		public void onSuccess(Integer integer) {
			mUiCallBack.onDelete(bookInfoList);
		}
	}).addOnFailureListener(new OnFailureListener() {
		@Override
		public void onFailure(Exception e) {
			mUiCallBack.updateUiOnError("Delete book info failed");
		}
	});
}
// bookInfoList is the dataset returned by the query operation or listener.
fun deleteBookInfosAsync(bookInfoList: List<BookInfo?>?) {
    if (mCloudDBZone == null) {
        Log.w(TAG, "CloudDBZone is null, try re-open it")
        return
    }
    val deleteTask = mCloudDBZone!!.executeDelete(bookInfoList!!)
    deleteTask.addOnSuccessListener {
        mUiCallBack.onDelete(bookInfoList)
    }.addOnFailureListener {
        mUiCallBack.updateUiOnError("Delete book info failed")
    }
}

Listening to Real-Time Data Changes

You can use the subscribeSnapshot() method to listen to data changes that meet the specified query conditions. When data changes, Cloud DB will trigger your custom callback function and transfer the data to the callback function as snapshots for you to process based on the service logic.

After a snapshot listener is registered, a certain period of time (depending on the network status) is needed to activate it. Therefore, data changes before the listener takes effect are not sent to the device. After the listener takes effect, the device can receive data change notifications from the cloud. After you register a snapshot listener, a snapshot is generated. Each time the data listened to changes, a snapshot is generated. When registering a listener, you need to specify the data source based on the data query policy. The data sources are as follows:
  • Cloud DB zone on the cloud
  • Both Cloud DB zone on the cloud and local cache

    When a data query policy is executed, the registered snapshot listener is immediately called when you add, delete, or modify data locally, instead of waiting for data synchronization to the cloud.

You can call the getUpsertedObjects() method in the CloudDBZoneSnapshot class to obtain the objects added or modified, the getDeletedObjects() method for objects deleted, compared with the last snapshot, and the release() method to free up resources occupied by the snapshot.

Prerequisites:

The Cloud DB zone uses cache for data synchronization.

Precautions:

NOTICE

Read the precautions carefully.

  • When the snapshot callback function is implemented through onSnapshot() in the OnSnapshotListener class, resources must be freed up by explicitly calling release() of the CloudDBZoneSnapshot class in onSnapshot(); otherwise, the latest snapshot object cannot be obtained, and the snapshot listener cannot be deregistered.
  • When an app user initiates a sign-out operation and their personal data snapshot listener has been registered, you need to use the remove() method in the app code to proactively deregister the user's data snapshot listener to ensure the correctness of data snapshot push.

Listening to Data Changes on the Cloud

Add a listener for cloud data changes. When data on the cloud changes, a snapshot is generated so that you can obtain data change information through the snapshot. After the snapshot is used, resources must be freed up by explicitly calling release() of the CloudDBZoneSnapshot class; otherwise, the latest snapshot object cannot be obtained and the snapshot listener cannot be deregistered.

  • Create a listener named mSnapshotListener.
    Java
    Kotlin
    private OnSnapshotListener<BookInfo> mSnapshotListener = new OnSnapshotListener<BookInfo>() {
    	@Override
    	public void onSnapshot(CloudDBZoneSnapshot<BookInfo> cloudDBZoneSnapshot, AGConnectCloudDBException e) {
    		if (e != null) {
    			Log.w(TAG, "onSnapshot: " + e.getMessage());
    			return;
    		}
    		CloudDBZoneObjectList<BookInfo> snapshotObjects = cloudDBZoneSnapshot.getSnapshotObjects();
    		List<BookInfo> bookInfos = new ArrayList<>();
    		try {
    			if (snapshotObjects != null) {
    				while (snapshotObjects.hasNext()) {
    					BookInfo bookInfo = snapshotObjects.next();
    					bookInfos.add(bookInfo);
    					updateBookIndex(bookInfo);
    				}
    			}
                            mUiCallBack.onSubscribe(bookInfos);
    		} catch (AGConnectCloudDBException snapshotException) {
    			Log.w(TAG, "onSnapshot:(getObject) " + snapshotException.getMessage());
    		} finally {
    			cloudDBZoneSnapshot.release();
    		}
    	}
    };
    private val mSnapshotListener = OnSnapshotListener<BookInfo> { cloudDBZoneSnapshot, e ->
        if (e != null) {
            Log.w(TAG, "onSnapshot: " + e.message)
            return@OnSnapshotListener
        }
        val snapshotObjects = cloudDBZoneSnapshot.snapshotObjects
        val bookInfoList: MutableList<BookInfo> = ArrayList()
        try {
            if (snapshotObjects != null) {
                while (snapshotObjects.hasNext()) {
                    val bookInfo = snapshotObjects.next()
                    bookInfoList.add(bookInfo)
                    updateBookIndex(bookInfo)
                }
            }
            mUiCallBack.onSubscribe(bookInfoList)
        } catch (snapshotException: AGConnectCloudDBException) {
            Log.w(TAG, "onSnapshot:(getObject) " + snapshotException.message)
        } finally {
            cloudDBZoneSnapshot.release()
        }
    }
  • Add a listener for cloud data changes. When data on the cloud changes, a snapshot is generated so that you can obtain data change information through the snapshot.
    Java
    Kotlin
    public void addSubscription() {
    	if (mCloudDBZone == null) {
    		Log.w(TAG, "CloudDBZone is null, try re-open it");
    		return;
    	}
    
    	try {
    		CloudDBZoneQuery<BookInfo> snapshotQuery = CloudDBZoneQuery.where(BookInfo.class)
    			.equalTo(BookEditFields.SHADOW_FLAG, true);
    		mRegister = mCloudDBZone.subscribeSnapshot(snapshotQuery,
    			CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY, mSnapshotListener);
    	} catch (AGConnectCloudDBException e) {
    		Log.w(TAG, "subscribeSnapshot: " + e.getMessage());
    	}
    }
    private fun addSubscription() {
        if (mCloudDBZone == null) {
            Log.w(TAG, "CloudDBZone is null, try re-open it")
            return
        }
    
        try {
            val snapshotQuery = CloudDBZoneQuery.where(BookInfo::class.java)
                    .equalTo(BookEditFields.SHADOW_FLAG, true)
            mRegister = mCloudDBZone!!.subscribeSnapshot(snapshotQuery,
                    CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY, mSnapshotListener)
        } catch (e: AGConnectCloudDBException) {
            Log.w(TAG, "subscribeSnapshot: " + e.message)
        }
    }

Deregistering a Listener

A listener can be deregistered by calling remove() in the ListenerHandler class.

Java
Kotlin
mRegister.remove();
mRegister!!.remove()

Managing Transactions and Data

Cloud DB can manage data in the Cloud DB zone on the cloud by calling runTransaction(), including adding, deleting, modifying, and querying data. You can add, delete, modify, and query data of multiple object types in a transaction at the same time. Transactions are atomic. Operations in a transaction either all succeed or all fail to be executed. If concurrent writes occur during transaction execution, Cloud DB will attempt to execute the entire transaction again to ensure the consistency of the entire transaction.

Prerequisites:

The Cloud DB zone uses cache for data synchronization.

Precautions:

NOTICE

Read the precautions carefully.

  • You can execute a transaction only when the Cloud DB zone is opened.
  • The query operation in a transaction must be performed before the write operation.
  • A transaction can be executed only when the network is properly connected.
  • The total size of multiple query results in a transaction cannot exceed 5 MB.
  • The total number of query results in a transaction cannot exceed 1000.
  • The total number of written and deleted object records contained in a transaction cannot exceed 1000.
  • The data size of multiple write and delete operations in a transaction cannot exceed 2 MB.

Sample code:

Delete books published earlier than 1990 from the cloud through a transaction as follows:
Java
Kotlin
// Define a transaction that encapsulates the query and delete operations.
public void deleteOverdueBooks(CloudDBZoneQuery<BookInfo> query) {
	Transaction.Function function = new Transaction.Function() {
		@Override
		public boolean apply(Transaction transaction) {
			try {
				List<BookInfo> bookInfos = transaction.executeQuery(query);
				transaction.executeDelete(bookInfos);
			} catch (AGConnectCloudDBException e) {
				e.printStackTrace();
				return false;
			}
			return true;
		}
	};
	mCloudDBZone.runTransaction(function);
}

// Construct a query condition and call the defined transaction to complete the delete operation.
try {
	CloudDBZoneQuery<BookInfo> query = CloudDBZoneQuery.where(BookInfo.class)
		.lessThan(BookEditFields.PUBLISH_TIME, DateUtils.parseDate("1990-01-01"));
	deleteOverdueBooks(query);
} catch (AGConnectCloudDBException e) {
	e.printStackTrace();
}
// Define a transaction that encapsulates the query and delete operations.
fun deleteOverdueBooks(query: CloudDBZoneQuery<BookInfo>) {
    val function = Transaction.Function(fun(transaction): Boolean {
        try {
            val bookInfos = transaction.executeQuery(query)
            transaction.executeDelete(bookInfos)
        } catch (e: AGConnectCloudDBException) {
            Log.w(TAG, "Transaction error, info: " + Log.getStackTraceString(e))
            return false
        }
        return true
    })
    mCloudDBZone!!.runTransaction(function)
}

// Construct a query condition and call the defined transaction to complete the delete operation.
try {
    val query = CloudDBZoneQuery.where(BookInfo::class.java)
            .lessThan(BookEditFields.PUBLISH_TIME, DateUtils.parseDate("1990-01-01"))
    deleteOverdueBooks(query)
} catch (e: AGConnectCloudDBException) {
    Log.w(TAG, "Exception occurred, info: " + Log.getStackTraceString(e))
}

Implementing Full-Process Encryption

Cloud DB provides full-process encryption management for private or sensitive data. After this function is enabled, user data is encrypted on the device, sent in ciphertext, and stored on the cloud. Only the user who enters the password can obtain information about their key and access their encrypted data. In this way, user data security is effectively improved, and user data on the cloud is prevented from being leaked.

You can create an object type and set an encryption field in AppGallery Connect to enable the full-process encryption function for the field. After importing an object type file to the local development environment, you need to use the setUserKey() method to set a user password. Cloud DB uses the password to derive information about the key and saves the encrypted information and user data in ciphertext on the cloud. The encryption key is bound to the user account. Only the user who enters the password can obtain information about the key and access the encrypted data. User passwords are saved by users and will not be synchronized to the cloud.

Precautions:

NOTICE

Read the precautions carefully.

  • If the setUserKey() method is not called to set the user password, the data of an object type cannot be synchronized to the cloud and operations cannot be performed on data of this object type on the cloud.
  • Cloud DB does not store user passwords. You need to customize password management policies by yourself.
  • When you query data encrypted in the entire process, the supported query predicates include equalTo(), notEqualTo(), isNull(), isNotNull(), greaterThan(), greaterThanOrEqualTo(), lessThan(), lessThanOrEqualTo(), orderByAsc(), orderByDesc(), limit(), startAt(), startAfter(), endAt(), and endBefore(). In addition, the executeCountQuery() method and the relational predicates and(), or(), beginGroup(), and endGroup() in the aggregate query are supported.
  • After full-process encryption is enabled, you are advised to enable local database encryption on the device to ensure local data security.
  • Custom configuration of user password verification strength is allowed when the user password for Cloud DB full-process encryption is set or changed. If strong verification is enabled, when a user password is set or changed for the first time, strong verification is performed on the user password. In other scenarios, weak verification is performed by default.
  • A user password can be changed only after the original password is verified successfully. If the original user password is not verified or the verification fails, operations on the encrypted data will fail.

Setting a User Password

  • After the full-process encryption function is enabled, use the setUserKey() method to set the initial user password to xxxxxxxxx.
    Java
    Kotlin
    Task<Boolean> setUserKeyTask = mCloudDB.setUserKey("xxxxxxxxx", null, true);
    setUserKeyTask.addOnCompleteListener(task -> {
    	StringBuilder builder = new StringBuilder();
    	if (task.getException() != null) {
    		builder.append("exception : ").append(task.getException());
    		mResultInfo.setText(builder.toString());
    		return;
    	}
    	boolean isSuccess = task.getResult();
    	if (isSuccess) {
    		updateUi("set user key success.");
    	} else {
    		updateUi("set user key fail.");
    	}
    });
    val setUserKeyTask = mCloudDB.setUserKey("xxxxxxxxx", null, true)
    setUserKeyTask.addOnCompleteListener { task ->
        val builder = StringBuilder()
        if (task.exception != null) {
            builder.append("exception : ").append(task.exception)
            mResultInfo.setText(builder.toString())
            return@addOnCompleteListener
        }
        val isSuccess = task.result
        if (isSuccess) {
            updateUi("set user key success.")
        } else {
            updateUi("set user key fail.")
        }
    }
  • Assume that there are two mobile phones. If the password of one mobile phone is changed, the other mobile phone can listen to the change through addEventListener(). After the change, the other mobile phone verifies the new password. Change the password from xxxxxxxxx to xxxxxxxxx.
    Java
    Kotlin
    String userReKey = "xxxxxxxxx";
    mCloudDB.addEventListener(new AGConnectCloudDB.EventListener() {
    	@Override
    	public void onEvent(AGConnectCloudDB.EventType eventType) {
    		if (eventType.equals(AGConnectCloudDB.EventType.USER_KEY_CHANGED)) {
    			Task<Boolean> setUserKeyTask = mCloudDB.setUserKey(userReKey, null, true);
    			setUserKeyTask.addOnCompleteListener(task -> {
    				StringBuilder builder = new StringBuilder();
    				if (task.getException() != null) {
    					builder.append("exception : ").append(task.getException());
    					mResultInfo.setText(builder.toString());
    					return;
    				}
    				boolean isSuccess = task.getResult();
    				if (isSuccess) {
    					updateUi("set user key success.");
    				} else {
    					updateUi("set user key fail.");
    				}
    			});
    		}
    	}
    });
    val userReKey = "xxxxxxxxx"
    mCloudDB.addEventListener { eventType ->
        if (eventType == AGConnectCloudDB.EventType.USER_KEY_CHANGED) {
            val setUserKeyTask = mCloudDB.setUserKey(userReKey, null, true)
            setUserKeyTask.addOnCompleteListener { task ->
                val builder = StringBuilder()
                if (task.exception != null) {
                    builder.append("exception : ").append(task.exception)
                    mResultInfo.setText(builder.toString())
                    return@addOnCompleteListener
                }
                val isSuccess = task.result
                if (isSuccess) {
                    updateUi("set user key success.")
                } else {
                    updateUi("set user key fail.")
                }
            }
        }
    }

Updating a Data Key

  • Use the updateDataEncryptionKey() method to update a data key.
    Java
    Kotlin
    Task<Boolean> updateDataEncryptionKeyTask = mCloudDB.updateDataEncryptionKey();
    updateDataEncryptionKeyTask.addOnCompleteListener(task -> {
    	StringBuilder builder = new StringBuilder();
    	if (task.getException() != null) {
    		builder.append("exception : ").append(task.getException());
    		mResultInfo.setText(builder.toString());
    		return;
    	}
    	boolean isSuccess = task.getResult();
    	if (isSuccess) {
    		updateUi("update data key success.");
    	} else {
    		updateUi("update data key fail.");
    	}
    });
    val updateDataEncryptionKeyTask = mCloudDB.updateDataEncryptionKey();
    updateDataEncryptionKeyTask.addOnCompleteListener { task ->
        val builder = StringBuilder()
        if (task.exception != null) {
            builder.append("exception : ").append(task.exception)
            mResultInfo.setText(builder.toString())
            return@addOnCompleteListener
        }
        val isSuccess = task.result
        if (isSuccess) {
            updateUi("update data key success.")
        } else {
            updateUi("update data key fail.")
        }
    }
  • Assume that there are two mobile phones. If the data key of one mobile phone is changed, the other mobile phone can listen to the change through addDataEncryptionKeyListener(). After the change, the data key of the other mobile phone is updated.
    Java
    Kotlin
    mCloudDB.addDataEncryptionKeyListener(new AGConnectCloudDB.OnDataEncryptionKeyChangeListener() {
    	@Override
    	public boolean needFetchDataEncryptionKey() {
    		return true;
    	}
    });
    mCloudDB.addDataEncryptionKeyListener( fun(): Boolean {
        return true
    })

Encrypting Local Data on the Device

Cloud DB provides a function to encrypt local data by Cloud DB zone. You can set the encryption key when creating a CloudDBZoneConfig object to meet your needs. You can call setEncryptedKey() to set or change the encryption key.

Precautions:

The encryption mode cannot be changed. If a Cloud DB zone is encrypted when opened for the first time, the encryption key will be required every time the Cloud DB zone is opened. Similarly, if a Cloud DB zone is not encrypted when opened for the first time, it will not be encrypted every time it is opened in the future.

Sample code:

  • Encrypt the Cloud DB zone.

    The initial key is xxxxxxxxx, which is passed through setEncryptedKey(). Then open the Cloud DB zone.

    Java
    Kotlin
    mConfig = new CloudDBZoneConfig("QuickStartDemo",
    	CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
    	CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
    // The first parameter indicates the initial key xxxxxxxxx, and the second parameter indicates the new key. If the second parameter is left empty, the key is not changed.
    mConfig.setEncryptedKey("xxxxxxxxx", "");
    mConfig.setPersistenceEnabled(true);
    ...
    Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
    openDBZoneTask.addOnSuccessListener( cloudDBZone -> {
    	Log.i(TAG, "Open cloudDBZone success");
    	mCloudDBZone = cloudDBZone;
    }).addOnFailureListener( e -> {
    	Log.w(TAG, Objects.requireNonNull(e.getMessage()));
    });
    mConfig = CloudDBZoneConfig("QuickStartDemo",
            CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
            CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC)
    // The first parameter indicates the initial key xxxxxxxxx, and the second parameter indicates the new key. If the second parameter is left empty, the key is not changed.
    mConfig!!.setEncryptedKey("xxxxxxxxx", "")
    mConfig!!.persistenceEnabled = true
    ...
    val openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig!!, true)
    openDBZoneTask.addOnSuccessListener { cloudDBZone ->
        Log.i(TAG, "Open cloudDBZone success")
        mCloudDBZone = cloudDBZone
    }.addOnFailureListener { e ->
        Log.w(TAG, Objects.requireNonNull(e.message))
    }
  • Change the encryption key of the Cloud DB zone.

    The first parameter of setEncryptedKey() is the initial key, and the second parameter is the new key. After the change, open the Cloud DB zone again.

    Java
    Kotlin
    mConfig = new CloudDBZoneConfig("QuickStartDemo",
    	CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
    	CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
    // Change the key from xxxxxxxxx to xxxxxxxxx.
    mConfig.setEncryptedKey("xxxxxxxxx", "xxxxxxxxx");
    mConfig.setPersistenceEnabled(true);
    ...
    Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
    openDBZoneTask.addOnSuccessListener( cloudDBZone -> {
    	Log.i(TAG, "Open cloudDBZone success");
    	mCloudDBZone = cloudDBZone;
    }).addOnFailureListener( e -> {
    	Log.w(TAG, Objects.requireNonNull(e.getMessage()));
    });
    mConfig = CloudDBZoneConfig("QuickStartDemo",
            CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
            CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC)
    // Change the key from xxxxxxxxx to xxxxxxxxx.
    mConfig!!.setEncryptedKey("xxxxxxxxx", "xxxxxxxxx")
    mConfig!!.persistenceEnabled = true
    ...
    val openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig!!, true);
    openDBZoneTask.addOnSuccessListener { cloudDBZone ->
        Log.i(TAG, "Open cloudDBZone success")
        mCloudDBZone = cloudDBZone
    }.addOnFailureListener { e ->
        Log.w(TAG, Objects.requireNonNull(e.message))
    }

Managing Cloud DB Zones

Each Cloud DB zone is a data storage zone that is independent of each other. Data in different Cloud DB zones is not associated. Multiple Cloud DB zones can be created in a Cloud DB instance.

Cloud DB synchronizes data in cache or local mode by Cloud DB zone. When developing an app, you can select a data synchronization mode for each Cloud DB zone based on app requirements. The data that can be managed varies according to the data synchronization mode.

  • Cache mode

    In this mode, you can implement device-cloud data synergy management so that data can be synchronized between the device and cloud or among multiple devices. App data is stored on the cloud, and data on the device is a subset of data on the cloud. If persistent cache is allowed, Cloud DB supports the automatic caching of query results on the device. When data is cached, the local data that is not synchronized will not be overwritten.

  • Local mode

    In this mode, users can only operate the local device data, and data cannot be synchronized between the device and cloud or among multiple devices.

Multiple object types can be defined in a Cloud DB zone to support different service requirements. Each Cloud DB zone has the same object type definition. You can customize the objects stored in the Cloud DB zone based on app requirements.

If any Cloud DB zone is not required, you can close it temporarily. You can also delete the Cloud DB zone to free up resources.

  • In local mode, deleting a Cloud DB zone will delete data files stored on the disk. The deleted data cannot be restored.
  • In cache mode, deleting a Cloud DB zone will delete data files stored on the device only. You can create a Cloud DB zone again to obtain data from the cloud.

Precautions:

NOTICE

Read the precautions carefully.

  • You can delete a Cloud DB zone only when it is closed.
  • You can perform operations on a Cloud DB zone only after you create or open it.
  • You can open a Cloud DB zone multiple times, but need to close it after each operation is complete. That is, opening a Cloud DB zone must be followed by a close operation.
  • After a Cloud DB zone is created, its data synchronization property in CloudDBZoneConfig cannot be modified. After you create and open a Cloud DB zone, Cloud DB checks whether the data synchronization property specified in CloudDBZoneConfig is the same as that specified during CloudDBZoneConfig creation. If they are different, Cloud DB will return an exception.

Sample code:

  • Create or open a Cloud DB zone whose data synchronization property is cache mode.
    Java
    Kotlin
    mConfig = new CloudDBZoneConfig("QuickStartDemo",
    	CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
    	CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
    mConfig.setPersistenceEnabled(true);
    Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
    openDBZoneTask.addOnSuccessListener( cloudDBZone -> {
    	Log.i(TAG, "Open cloudDBZone success");
    	mCloudDBZone = cloudDBZone;
    }).addOnFailureListener( e -> {
    	Log.w(TAG, "Open cloudDBZone failed for " + e.getMessage());
    });
    mConfig = CloudDBZoneConfig("QuickStartDemo",
            CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
            CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC)
    mConfig!!.persistenceEnabled = true
    val openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig!!, true)
    openDBZoneTask.addOnSuccessListener { cloudDBZone ->
        Log.i(TAG, "Open cloudDBZone success")
        mCloudDBZone = cloudDBZone
    }.addOnFailureListener { e ->
        Log.w(TAG, "Open cloudDBZone failed for " + e.message)
    }
  • Create or open a Cloud DB zone whose data synchronization property is local mode.
    Java
    Kotlin
    mConfig = new CloudDBZoneConfig("QuickStartDemo",
    	CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_LOCAL_ONLY,
    	CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
    mConfig.setPersistenceEnabled(true);
    Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
    openDBZoneTask.addOnSuccessListener( cloudDBZone -> {
    	Log.i(TAG, "Open cloudDBZone success");
    	mCloudDBZone = cloudDBZone;
    }).addOnFailureListener( e -> {
    	Log.w(TAG, "Open cloudDBZone failed for " + e.getMessage());
    });
    mConfig = CloudDBZoneConfig("QuickStartDemo",
            CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_LOCAL_ONLY,
            CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC)
    mConfig!!.persistenceEnabled = true
    val openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig!!, true)
    openDBZoneTask.addOnSuccessListener { cloudDBZone ->
        Log.i(TAG, "Open cloudDBZone success")
        mCloudDBZone = cloudDBZone
    }.addOnFailureListener {
        Log.w(TAG, "Open cloudDBZone failed for " + it.message)
    }
  • Close the Cloud DB zone.
    Java
    Kotlin
    mCloudDB.closeCloudDBZone(mCloudDBZone);
    mCloudDB.closeCloudDBZone(mCloudDBZone)
  • Delete the Cloud DB zone.

    This method will also delete local data. Confirm the operation before a call.

    Java
    Kotlin
    mCloudDB.deleteCloudDBZone(mConfig.getCloudDBZoneName());
    mCloudDB.deleteCloudDBZone(mConfig!!.cloudDBZoneName)

Managing Version Updates

During an app update, you can export the corresponding version file from AppGallery Connect and add it to the local development environment. Call the createObjectType() method in the AGConnectCloudDB class to create an object type, and call the openCloudDBZone2() method to open the specified Cloud DB zone and update the object type version. After the on-device version is updated, new fields in the object type are left empty or use the default values, while data of the existing fields remains unchanged. In cache mode, when the device queries data on the cloud, the latest data is updated to the local device based on the query result. If the device does not query data on the cloud, then data on the cloud will not be updated to the device. In this case, data on the device will remain unchanged after the update. That is, new fields are left empty or use the default values, while data of the existing fields remains unchanged.

Search
Enter a keyword.