ARCore is a platform for building augmented reality apps on mobile devices. Cloud Anchors gives you the ability to create AR apps that share a common frame-of-reference, enabling multiple users to place virtual content in the same real world location.
This codelab guides you through the Cloud Anchors API. You will take an existing ARCore app, modify it to use Cloud Anchors, and create a shared AR experience.
A fundamental concept in ARCore is that of an Anchor, which describes a fixed position in the real world. The value of an Anchor's pose is automatically adjusted by ARCore as its motion tracking improves over time.
Cloud Anchors are Anchors that are hosted in the cloud and can be resolved by multiple users to establish a common frame of reference across users and their devices.
When an anchor is hosted, the following things happen:
After they are hosted, Cloud Anchors are not retained on Google servers for longer than 24 hours.
In this codelab, Cloud Anchor IDs are transferred using Firebase. You are free to share Cloud Anchor IDs using other means.
We can use the Cloud Anchor API to resolve an anchor using its Cloud Anchor ID. This creates a new anchor in the same physical location as the original hosted anchor. While resolving, the device must be looking at the same physical environment where the original hosted anchor was.
In this codelab, you're going to build upon a pre-existing ARCore app. By the end of the codelab, your app will:
| An Android statue is rendered at the position of the Cloud Anchor. |
Connect your ARCore device to your computer via the USB cable. Make sure that your device allows USB debugging. Open a terminal and run adb devices
, as shown below:
adb devices List of devices attached <DEVICE_SERIAL_NUMBER> device
The <DEVICE_SERIAL_NUMBER> will be a string unique to your device. Make sure that you see exactly one device before continuing.
You can either clone the repository:
git clone https://github.com/googlecodelabs/arcore-cloud-anchors.git
Or download a ZIP file and extract it:
Launch Android Studio. Click Open an existing Android Studio project. Then, navigate to the directory where you extracted the ZIP file downloaded above, and double-click on the arcore-cloud-anchors
directory.
This is a single Gradle project with multiple modules. If the Project pane on the top left of Android Studio isn't already displayed in the Project pane, click Projects from the drop-down menu. The result should like this:
You will work primarily in the work
module. Other modules include a helpers
module which contains a set of useful wrapper classes that you will use. There are also complete solutions for each part of the codelab. Except for the helpers
module, each module is a buildable app.
If you see a dialog recommending that you upgrade the Android Gradle Plugin, click Dont remind me again for this project:
Click Run > Run... > 'work'. In the Select Deployment Target dialog that displays, your device should be listed under Connected Devices. Select your device and click OK. Android Studio will build the initial app and run it on your device.
When you run the app for the first time, it will request the CAMERA permission. Tap ALLOW to continue. |
How to use the app
|
This app, right now, is only using the motion tracking provided by ARCore to track an anchor in a single run of the app. If you decide to quit the app, kill it, and then restart the app, the previously placed anchor and any information related to it, including its pose, is lost.
In the next few sections, we're going to build on this app to see how anchors can be shared across AR sessions.
In this section you will modify the work
project to host an anchor. Before we write code, we need to implement a few modifications to the app's configuration.
Because Cloud Anchors require communication with a cloud service, your app must have permission to access the internet.
In your AndroidManifest.xml
file, add the following line just below the android.permission.CAMERA
permission declaration:
<!-- Find this line... -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- Add the line right below -->
<uses-permission android:name="android.permission.INTERNET"/>
To use Cloud Anchors, you'll need to add an API Key to your app for authentication with the ARCore Cloud Anchor Service. Follow Steps 1 and 2 from these instructions to get an API Key.
Include the API key in your AndroidManifest.xml
file as follows:
<!-- Find this line... -->
<meta-data android:name="com.google.ar.core" android:value="required" />
<!-- Add the lines right below -->
<meta-data
android:name="com.google.android.ar.API_KEY"
android:value="<YOUR API KEY HERE>"/>
Next, we'll modify the app to create a hosted anchor on a user tap instead of a regular one. To do that, you will need to configure the ARCore Session to enable Cloud Anchors.
In the CloudAnchorFragment.java
file, add the following method:
@Override
protected Config getSessionConfiguration(Session session) {
Config config = super.getSessionConfiguration(session);
config.setCloudAnchorMode(CloudAnchorMode.ENABLED);
return config;
}
Don't forget to add the new imports at the top of your file, if Android Studio didn't do them automatically:
// Add these lines at the top with the rest of the imports.
import com.google.ar.core.Config;
import com.google.ar.core.Config.CloudAnchorMode;
import com.google.ar.core.Session;
Before proceeding further, build and run your app. Make sure to only build the work
module. Your app should build successfully and be running the same as before.
It's time to create a hosted anchor that will be uploaded to the ARCore Cloud Anchor Service.
Add the following new fields to your CloudAnchorFragment
class:
// Add these lines in the CloudAnchorFragment class.
private final CloudAnchorManager cloudAnchorManager = new CloudAnchorManager();
private final SnackbarHelper snackbarHelper = new SnackbarHelper();
Don't forget to add the new imports at the top of your file, if Android Studio didn't do them automatically:
// Add these lines at the top with the rest of the imports.
import com.google.ar.core.codelab.cloudanchor.helpers.CloudAnchorManager;
import com.google.ar.core.codelab.cloudanchor.helpers.SnackbarHelper;
The CloudAnchorManager
and SnackbarHelper
classes have already been provided to you. These are some useful wrapper classes that encapsulate boilerplate code so the code you write is much cleaner and less verbose.
In the onCreateView
method, add the line mentioned below:
// Find this line...
arScene = getArSceneView().getScene();
// Add this line right below:
arScene.addOnUpdateListener(frameTime -> cloudAnchorManager.onUpdate());
Modify the onClearButtonPressed
method as follows:
private synchronized void onClearButtonPressed() {
// Clear the anchor from the scene.
// The next line is the new addition.
cloudAnchorManager.clearListeners();
setNewAnchor(null);
}
Next, add the following method to your CloudAnchorFragment
class:
private synchronized void onHostedAnchorAvailable(Anchor anchor) {
CloudAnchorState cloudState = anchor.getCloudAnchorState();
if (cloudState == CloudAnchorState.SUCCESS) {
snackbarHelper.showMessage(
getActivity(), "Cloud Anchor Hosted. ID: " + anchor.getCloudAnchorId());
setNewAnchor(anchor);
} else {
snackbarHelper.showMessage(getActivity(), "Error while hosting: " + cloudState.toString());
}
}
Don't forget to add the new imports at the top of your file, if Android Studio didn't do them automatically:
// Add these lines at the top with the rest of the imports.
import com.google.ar.core.Anchor.CloudAnchorState;
Find the onArPlaneTap
method in the CloudAnchorFragment
class, and add the lines mentioned below:
// Find these two lines...
Anchor anchor = hitResult.createAnchor();
setNewAnchor(anchor);
// Add these lines right below:
snackbarHelper.showMessage(getActivity(), "Now hosting anchor...");
cloudAnchorManager.hostCloudAnchor(
getArSceneView().getSession(), anchor, this::onHostedAnchorAvailable);
Run your app from Android Studio again. You should see the message "Now hosting anchor..." when you place an anchor. You should see another message when the hosting completes successfully. If you see "Error hosting anchor: ERROR_NOT_AUTHORIZED", verify that your AndroidManifest.xml
contains a valid API key.
Immediately after placing an anchor | After waiting for a bit |
Anyone who knows the anchor ID and is present in the same physical space as the anchor can use the anchor ID to create an anchor at the exact same pose (position and orientation) relative to the environment around them.
However, the anchor ID is long, and not easy for another user to enter manually. In the following sections, we show how to store Cloud Anchor IDs in an easy-to-retrieve fashion in order to allow anchor resolving on the same or another device.
In this part, we assign short codes to the long Cloud Anchor IDs to make it easier for another user to enter manually. We store the Cloud Anchor IDs as values in a key-value table, using the Shared Preferences API; the table will persist even if the app is killed and restarted.
A helper class called StorageManager
is already provided for you. This is a wrapper around the SharedPreferences API that has methods for generating new unique short codes, and reading/writing Cloud Anchor IDs.
Here we modify MainActivity to use StorageManager to store Cloud Anchor IDs with short codes, so they can be retrieved easily.
Create the following new field in CloudAnchorFragment
:
private final StorageManager storageManager = new StorageManager();
Don't forget to add the new imports at the top of your file, if Android Studio didn't do them automatically:
// Add these lines at the top with the rest of the imports.
import com.google.ar.core.codelab.cloudanchor.helpers.StorageManager;
The first thing you need to do is modify the onHostedAnchorAvailable method as follows:
private synchronized void onHostedAnchorAvailable(Anchor anchor) {
CloudAnchorState cloudState = anchor.getCloudAnchorState();
if (cloudState == CloudAnchorState.SUCCESS) {
int shortCode = storageManager.nextShortCode(getActivity());
storageManager.storeUsingShortCode(getActivity(), shortCode, anchor.getCloudAnchorId());
snackbarHelper.showMessage(
getActivity(), "Cloud Anchor Hosted. Short code: " + shortCode);
setNewAnchor(anchor);
} else {
snackbarHelper.showMessage(getActivity(), "Error while hosting: " + cloudState.toString());
}
}
Now, build and run the app from Android Studio. You should see short codes being displayed instead of the long Cloud Anchor IDs when you create and host an anchor.
Immediately after placing an anchor | After waiting for a bit |
Note that the short codes generated by StorageManager
are currently always assigned in increasing order.
Next we add a few UI elements that will allow us to enter short codes and recreate the anchors.
We're going to add another button next to the CLEAR button. This will be the RESOLVE button. Clicking the RESOLVE button will open a dialog box that prompts the user for a short code. The short code is used to retrieve the Cloud Anchor ID from StorageManager
, and resolve the anchor.
To add the button, you will need to modify the res/layout/cloud_anchor_fragment.xml
file. Double click on the file in Android Studio, and then click on the "Text" tab at the bottom to display the raw XML. Make the following modifications:
<!-- Find this element... -->
<Button
android:text="CLEAR"
android:id="@+id/clear_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!-- Add this element right below. -->
<Button
android:text="RESOLVE"
android:id="@+id/resolve_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Now, add a new field to the CloudAnchorFragment
:
private Button resolveButton;
Add a new method:
private synchronized void onResolveButtonPressed() {
ResolveDialogFragment dialog = new ResolveDialogFragment();
dialog.show(getFragmentManager(), "Resolve");
}
Don't forget to add the new imports at the top of your file, if Android Studio didn't do them automatically:
// Add these lines at the top with the rest of the imports.
import com.google.ar.core.codelab.cloudanchor.helpers.ResolveDialogFragment;
Initialize resolveButton
in the onCreateView
method as follows:
// Find these lines...
Button clearButton = rootView.findViewById(R.id.clear_button);
clearButton.setOnClickListener(v -> onClearButtonPressed());
// Add these lines right below.
resolveButton = rootView.findViewById(R.id.resolve_button);
resolveButton.setOnClickListener(v -> onResolveButtonPressed());
Find the onArPlaneTap
method, and modify it to be as follows:
private synchronized void onArPlaneTap(HitResult hitResult) {
if (anchorNode != null) {
// Do nothing if there was already an anchor in the Scene.
return;
}
Anchor anchor = hitResult.createAnchor();
setNewAnchor(anchor);
// The next line is the new addition.
resolveButton.setEnabled(false);
snackbarHelper.showMessage(getActivity(), "Now hosting anchor...");
cloudAnchorManager.hostCloudAnchor(
getArSceneView().getSession(), anchor, this::onHostedAnchorAvailable);
}
And then, add a line in the onClearButtonPressed
method:
private synchronized void onClearButtonPressed() {
// Clear the anchor from the scene.
cloudAnchorManager.clearListeners();
// The next line is the new addition.
resolveButton.setEnabled(true);
setNewAnchor(null);
}
Now, build and run the app from Android Studio. You should see the RESOLVE button next to the CLEAR button. Clicking the RESOLVE button should result in a dialog popping up as shown below.
The RESOLVE button is now visible | Clicking the button causes this dialog to appear. |
Tapping on the plane and hosting an anchor should disable the RESOLVE button, but tapping on the CLEAR button should enable it again. This is by design, so that only one anchor is in the scene at a time.
The "Resolve Anchor" dialog does nothing, but we'll change that now.
In the CloudAnchorFragment
class, add the following methods:
private synchronized void onShortCodeEntered(int shortCode) {
String cloudAnchorId = storageManager.getCloudAnchorId(getActivity(), shortCode);
if (cloudAnchorId == null || cloudAnchorId.isEmpty()) {
snackbarHelper.showMessage(
getActivity(),
"A Cloud Anchor ID for the short code " + shortCode + " was not found.");
return;
}
resolveButton.setEnabled(false);
cloudAnchorManager.resolveCloudAnchor(
getArSceneView().getSession(),
cloudAnchorId,
anchor -> onResolvedAnchorAvailable(anchor, shortCode));
}
private synchronized void onResolvedAnchorAvailable(Anchor anchor, int shortCode) {
CloudAnchorState cloudState = anchor.getCloudAnchorState();
if (cloudState == CloudAnchorState.SUCCESS) {
snackbarHelper.showMessage(getActivity(), "Cloud Anchor Resolved. Short code: " + shortCode);
setNewAnchor(anchor);
} else {
snackbarHelper.showMessage(
getActivity(),
"Error while resolving anchor with short code "
+ shortCode
+ ". Error: "
+ cloudState.toString());
resolveButton.setEnabled(true);
}
}
Then, modify the onResolveButtonPressed method to be the following:
private synchronized void onResolveButtonPressed() {
ResolveDialogFragment dialog = ResolveDialogFragment.createWithOkListener(
this::onShortCodeEntered);;
dialog.show(getFragmentManager(), "Resolve");
}
Now, build and run the app from Android Studio, and perform the following steps:
Entering a short code | Anchor is successfully resolved |
You've seen how you can store the Cloud Anchor ID of an anchor to your device's local storage, and then retrieve it later to recreate the same anchor. But the full potential of Cloud Anchors is only unlocked when you can share the Cloud Anchor IDs between different devices.
How your app shares Cloud Anchor IDs is up to you. Anything can be used to transfer the string from one device to another. For this codelab, we use the Firebase Realtime Database to transfer Cloud Anchor IDs between instances of the app.
You need to set up a Firebase Realtime Database with your Google account to use with this app. This is easy with the Firebase Assistant in Android Studio.
In Android Studio, click on Tools > Firebase. In the Assistant pane that pops up, click Realtime Database, then click on Save and retrieve data:
Click on the Connect to Firebase button to connect your Android Studio project to a new or existing Firebase project.
This will prompt you to select a module. Select the work
module:
The Starting Connect dialog displays. This may take a little while.
Sign in with your Google account, and go through the web workflow for creating a Firebase project for your app until you're returned to Android Studio.
Next, in the Assistant pane, click add the Realtime Database to your app:
In the dialog that pops up, select work from the Target module drop-down, then click Accept Changes.
This will:
work
directoryIn the work
module build.gradle
file, find and remove the following line (the xxxx is a placeholder for the latest version number)
dependencies {
...
implementation 'com.google.firebase:firebase-database:xxxx'
Next, review (but don't follow yet) the instructions linked from the configure your rules for public access link to configure your Firebase Realtime Database to be world writable. This helps simplify testing in this codelab:
From the Firebase Console (https://console.firebase.google.com/), select the project you connected your Android Studio project to, then select DEVELOP > Database.
Click GET STARTED to configure and setup the Realtime Database:
If offered, select the test mode security rules and click ENABLE:
At any time, you can use the Database RULES tab to change your database's security rules:
Your app is now configured to use the Firebase database.
We will now replace the StorageManager
with the FirebaseManager
.
In Android Studio, find the CloudAnchorFragment
class under the work
directory, and create the following new field:
private FirebaseManager firebaseManager;
Don't forget to add the new imports at the top of your file, if Android Studio didn't do them automatically:
// Add these lines at the top with the rest of the imports.
import com.google.ar.core.codelab.cloudanchor.helpers.FirebaseManager;
Initialize firebaseManager
in the onAttach
method:
public void onAttach(Context context) {
super.onAttach(context);
ModelRenderable.builder()
.setSource(context, R.raw.andy)
.build()
.thenAccept(renderable -> andyRenderable = renderable);
// The next line is the new addition.
firebaseManager = new FirebaseManager(context);
}
Modify the onShortCodeEntered
method as follows:
private synchronized void onShortCodeEntered(int shortCode) {
firebaseManager.getCloudAnchorId(shortCode, cloudAnchorId -> {
if (cloudAnchorId == null || cloudAnchorId.isEmpty()) {
snackbarHelper.showMessage(
getActivity(),
"A Cloud Anchor ID for the short code " + shortCode + " was not found.");
return;
}
resolveButton.setEnabled(false);
cloudAnchorManager.resolveCloudAnchor(
getArSceneView().getSession(),
cloudAnchorId,
anchor -> onResolvedAnchorAvailable(anchor, shortCode));
});
}
Then, modify the onHostedAnchorAvailable
method as follows:
private synchronized void onHostedAnchorAvailable(Anchor anchor) {
CloudAnchorState cloudState = anchor.getCloudAnchorState();
if (cloudState == CloudAnchorState.SUCCESS) {
String cloudAnchorId = anchor.getCloudAnchorId();
firebaseManager.nextShortCode(shortCode -> {
if (shortCode != null) {
firebaseManager.storeUsingShortCode(shortCode, cloudAnchorId);
snackbarHelper
.showMessage(getActivity(), "Cloud Anchor Hosted. Short code: " + shortCode);
} else {
// Firebase could not provide a short code.
snackbarHelper
.showMessage(getActivity(), "Cloud Anchor Hosted, but could not "
+ "get a short code from Firebase.");
}
});
setNewAnchor(anchor);
} else {
snackbarHelper.showMessage(getActivity(), "Error while hosting: " + cloudState.toString());
}
}
Build and run your app. You should see the same UI flow as in the previous section, except that now, the online Firebase database is being used to store the Cloud Anchor IDs and short codes, instead of device-local storage.
To test what a multi-user experience is like, use two different phones:
You should be able to host anchors from one device, get a short code, and use the short code on the other device to see the anchor in the same place!
Congratulations! You've reached the end of this codelab!