Saved games for Android games

This guide shows you how to implement saved games using the snapshots API provided by Google Play Games Services. The APIs can be found in the com.google.android.gms.games.snapshot and com.google.android.gms.games packages.

Before you begin

For information about the feature, see the Saved Games overview.

Get the snapshots client

To start using the snapshots API, your game must first obtain a SnapshotsClient object. You can do this by calling the Games.getSnapshotsContents() method and passing in the activity.

Display saved games

You can integrate the snapshots API wherever your game provides players with the option to save or restore their progress. Your game might display such an option at designated save or restore points or allow players to save or restore progress at any time.

Once players select the save or restore option in your game, your game can optionally bring up a screen that prompts players to enter information for a new saved game or to select an existing saved game to restore.

To simplify your development, the snapshots API provides a default saved games selection user interface (UI) that you can use out-of-the-box. The saved games selection UI allows players to create a new saved game, view details about existing saved games, and load previous saved games.

To launch the default Saved Games UI:

  1. Call SnapshotsClient.getSelectSnapshotIntent() to get an Intent for launching the default saved games selection UI.
  2. Call startActivityForResult() and pass in that Intent. If the call is successful, the game displays the saved game selection UI, along with the options you specified.

Here's an example of how to launch the default saved games selection UI:

privatestaticfinalintRC_SAVED_GAMES=9009;
privatevoidshowSavedGamesUI(){
SnapshotsClientsnapshotsClient=
PlayGames.getSnapshotsClient(this);
intmaxNumberOfSavedGamesToShow=5;
Task<Intent>intentTask=snapshotsClient.getSelectSnapshotIntent(
"See My Saves",true,true,maxNumberOfSavedGamesToShow);
intentTask.addOnSuccessListener(newOnSuccessListener<Intent>(){
@Override
publicvoidonSuccess(Intentintent){
startActivityForResult(intent,RC_SAVED_GAMES);
}
});
}

If the player selects to create a new saved game or load an existing saved game, the UI sends a request to Play Games Services. If the request is successful, Play Games Services returns information to create or restore the saved game through the onActivityResult() callback. Your game can override this callback to check if any errors occurred during request.

The following code snippet shows a sample implementation of onActivityResult():

privateStringmCurrentSaveName="snapshotTemp";
/**
 * This callback will be triggered after you call startActivityForResult from the
 * showSavedGamesUI method.
 */
@Override
protectedvoidonActivityResult(intrequestCode,intresultCode,
Intentintent){
if(intent!=null){
if(intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA)){
//Loadasnapshot.
SnapshotMetadatasnapshotMetadata=
intent.getParcelableExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA);
mCurrentSaveName=snapshotMetadata.getUniqueName();
//LoadthegamedatafromtheSnapshot
//...
}elseif(intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_NEW)){
//Createanewsnapshotnamedwithauniquestring
Stringunique=newBigInteger(281,newRandom()).toString(13);
mCurrentSaveName="snapshotTemp-"+unique;
//Createthenewsnapshot
//...
}
}
}

Write saved games

To store content to a saved game:

  1. Asynchronously open a snapshot using SnapshotsClient.open().

  2. Retrieve the Snapshot object from the task's result by calling SnapshotsClient.DataOrConflict.getData().

  3. Retrieve a SnapshotContents instance with SnapshotsClient.SnapshotConflict.

  4. Call SnapshotContents.writeBytes() to store the player's data in byte format.

  5. Once all your changes are written, call SnapshotsClient.commitAndClose() to send your changes to Google's servers. In the method call, your game can optionally provide additional information to tell Play Games Services how to present this saved game to players. This information is represented in a SnapshotMetaDataChange object, which your game creates using SnapshotMetadataChange.Builder.

The following snippet shows how your game might commit changes to a saved game:

privateTask<SnapshotMetadata>writeSnapshot(Snapshotsnapshot,
byte[]data,BitmapcoverImage,Stringdesc){
//Setthedatapayloadforthesnapshot
snapshot.getSnapshotContents().writeBytes(data);
//Createthechangeoperation
SnapshotMetadataChangemetadataChange=newSnapshotMetadataChange.Builder()
.setCoverImage(coverImage)
.setDescription(desc)
.build();
SnapshotsClientsnapshotsClient=
PlayGames.getSnapshotsClient(this);
//Committheoperation
returnsnapshotsClient.commitAndClose(snapshot,metadataChange);
}

If the player's device is not connected to a network when your app calls SnapshotsClient.commitAndClose(), Play Games Services stores the saved game data locally on the device. Upon device re-connection, Play Games Services syncs the locally cached saved game changes to Google's servers.

Load saved games

To retrieve saved games for the authenticated player:

  1. Asynchronously open a snapshot with SnapshotsClient.open().

  2. Retrieve the Snapshot object from the task's result by calling SnapshotsClient.DataOrConflict.getData(). Alternatively, your game can also retrieve a specific snapshot through the saved games selection UI, as described in Display saved games.

  3. Retrieve the SnapshotContents instance with SnapshotsClient.SnapshotConflict.

  4. Call SnapshotContents.readFully() to read the contents of the snapshot.

The following snippet shows how you might load a specific saved game:

Task<byte[]>loadSnapshot(){
//Displayaprogressdialog
//...
//GettheSnapshotsClientfromthesignedinaccount.
SnapshotsClientsnapshotsClient=
PlayGames.getSnapshotsClient(this);
//Inthecaseofaconflict,themostrecentlymodifiedversionofthissnapshotwillbeused.
intconflictResolutionPolicy=SnapshotsClient.RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED;
//Openthesavedgameusingitsname.
returnsnapshotsClient.open(mCurrentSaveName,true,conflictResolutionPolicy)
.addOnFailureListener(newOnFailureListener(){
@Override
publicvoidonFailure(@NonNullExceptione){
Log.e(TAG,"Error while opening Snapshot.",e);
}
}).continueWith(newContinuation<SnapshotsClient.DataOrConflict<Snapshot>,byte[]>(){
@Override
publicbyte[]then(@NonNullTask<SnapshotsClient.DataOrConflict<Snapshot>>task)throwsException{
Snapshotsnapshot=task.getResult().getData();
//Openingthesnapshotwasasuccessandanyconflictshavebeenresolved.
try{
//Extracttherawdatafromthesnapshot.
returnsnapshot.getSnapshotContents().readFully();
}catch(IOExceptione){
Log.e(TAG,"Error while reading Snapshot.",e);
}
returnnull;
}
}).addOnCompleteListener(newOnCompleteListener<byte[]>(){
@Override
publicvoidonComplete(@NonNullTask<byte[]>task){
//DismissprogressdialogandreflectthechangesintheUIwhencomplete.
//...
}
});
}

Handle saved game conflicts

When using the snapshots API in your game, it is possible for multiple devices to perform reads and writes on the same saved game. In the event that a device temporarily loses its network connection and later reconnects, this might cause data conflicts whereby the saved game stored on a player's local device is out-of-sync with the remote version stored in Google's servers.

The snapshots API provides a conflict resolution mechanism that presents both sets of conflicting saved games at read-time and lets you implement a resolution strategy that is appropriate for your game.

When Play Games Services detects a data conflict, the SnapshotsClient.DataOrConflict.isConflict() method returns a value of true In this event, the SnapshotsClient.SnapshotConflict class provides two versions of the saved game:

  • Server version: The most-up-to-date version known by Play Games Services to be accurate for the player's device.

  • Local version: A modified version detected on one of the player's devices that contains conflicting content or metadata. This may not be the same as the version that you tried to save.

Your game must decide how to resolve the conflict by picking one of the provided versions or merging the data of the two saved game versions.

To detect and resolve saved game conflicts:

  1. Call SnapshotsClient.open(). The task result contains a SnapshotsClient.DataOrConflict class.

  2. Call the SnapshotsClient.DataOrConflict.isConflict() method. If the result is true, you have a conflict to resolve.

  3. Call SnapshotsClient.DataOrConflict.getConflict() to retrieve a SnapshotsClient.snapshotConflict instance.

  4. Call SnapshotsClient.SnapshotConflict.getConflictId() to retrieve the conflict ID that uniquely identifies the detected conflict. Your game needs this value to send a conflict resolution request later.

  5. Call SnapshotsClient.SnapshotConflict.getConflictingSnapshot() to get the local version.

  6. Call SnapshotsClient.SnapshotConflict.getSnapshot() to get the server version.

  7. To resolve the saved game conflict, select a version that you want to save to the server as the final version, and pass it to the SnapshotsClient.resolveConflict() method.

The following snippet shows and example of how your game might handle a saved game conflict by selecting the most recently modified saved game as the final version to save:

privatestaticfinalintMAX_SNAPSHOT_RESOLVE_RETRIES=10;
Task<Snapshot>processSnapshotOpenResult(SnapshotsClient.DataOrConflict<Snapshot>result,
finalintretryCount){
if(!result.isConflict()){
// There was no conflict, so return the result of the source.
TaskCompletionSource<Snapshot>source=newTaskCompletionSource<>();
source.setResult(result.getData());
returnsource.getTask();
}
// There was a conflict. Try resolving it by selecting the newest of the conflicting snapshots.
// This is the same as using RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED as a conflict resolution
// policy, but we are implementing it as an example of a manual resolution.
// One option is to present a UI to the user to choose which snapshot to resolve.
SnapshotsClient.SnapshotConflictconflict=result.getConflict();
Snapshotsnapshot=conflict.getSnapshot();
SnapshotconflictSnapshot=conflict.getConflictingSnapshot();
// Resolve between conflicts by selecting the newest of the conflicting snapshots.
SnapshotresolvedSnapshot=snapshot;
if(snapshot.getMetadata().getLastModifiedTimestamp()<
conflictSnapshot.getMetadata().getLastModifiedTimestamp()){
resolvedSnapshot=conflictSnapshot;
}
returnPlayGames.getSnapshotsClient(theActivity)
.resolveConflict(conflict.getConflictId(),resolvedSnapshot)
.continueWithTask(
newContinuation<
SnapshotsClient.DataOrConflict<Snapshot>,
Task<Snapshot>>(){
@Override
publicTask<Snapshot>then(
@NonNullTask<SnapshotsClient.DataOrConflict<Snapshot>>task)
throwsException{
// Resolving the conflict may cause another conflict,
// so recurse and try another resolution.
if(retryCount < MAX_SNAPSHOT_RESOLVE_RETRIES){
returnprocessSnapshotOpenResult(task.getResult(),retryCount+1);
}else{
thrownewException("Could not resolve snapshot conflicts");
}
}
});
}

Modify saved games

If you want to merge data from multiple saved games or modify an existing Snapshot to save to the server as the resolved final version, follow these steps:

  1. Call SnapshotsClient.open().

  2. Call SnapshotsClient.SnapshotConflict.getResolutionSnapshotsContent() to get a new SnapshotContents object.

  3. Merge the data from SnapshotsClient.SnapshotConflict.getConflictingSnapshot() and SnapshotsClient.SnapshotConflict.getSnapshot() into the SnapshotContents object from the previous step.

  4. Optionally, create a SnapshotMetadataChange instance if there are any changes to the metadata fields.

  5. Call SnapshotsClient.resolveConflict(). In your method call, pass SnapshotsClient.SnapshotConflict.getConflictId() as the first argument, and the SnapshotMetadataChange and SnapshotContents objects that you modified earlier as the second and third arguments respectively.

  6. If the SnapshotsClient.resolveConflict() call is successful, the API stores the Snapshot object to the server and attempts to open the Snapshot object on your local device.

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2026年06月16日 UTC.