I have the following scenario in my app -
SherlockFragmentActivity hosting a ViewPager containing 3 individual fragments. There is a sherlockactionbar on top, with backup and restore options, which should basically backup the app data to google drive account (connected to the user).
Now, everything works fine, wrt the backup-restore as well, by opening up the Google Api Client connection and uploading / downloading file, but as soon as the device rotates, the Activity is restarted from scratch and the google api client instance created and instantiated ealier (may be upload / download is also still in progress) becomes orphan.
I get a IllegalStateException (due to the activity restart, while the instance still needs an original activity to tie back to).
Knowing, that the Fragments themselves can be retained across activity restarts, I can put the drive api implementation in fragment, but having a common action bar icon on top, wouldn't it be a repetition to just put drive implementation on all 3 fragments??
Also, when the ViewPager swipe occurs, the fragment will be changed, so wouldn't I face the same issue again, wherein the original drive api client instance creator fragment is no longer in scope.?
What is a good way to implement such a thing? Any ideas would help me to research in given direction.
I have a viewpager and several fragments, just as your case. I let the my activity class extend the BaseDriveActivity from the Google-drive-exapmle, but letting it extend FragmentActivity instead of Activity.
The rotation was not any problem, even if onResume is called during rotation, sinde it is checking if mGoogleApiClient is null:
#Override
protected void onResume() {
super.onResume();
if (mGoogleApiClient == null) {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API).addScope(Drive.SCOPE_FILE)
.addScope(Drive.SCOPE_APPFOLDER)
// required for App Folder sample
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).build();
}
if (!mGoogleApiClient.isConnected() && !mGoogleApiClient.isConnecting())
mGoogleApiClient.connect();
}
There is some logic for errorhandling as well. You can use it as is.
The operations for creating a folder, creating a folder inside a foldet etc I added to my main class like this:
protected void createFolder(String folderName){
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(folderName).build();
Drive.DriveApi.getRootFolder(getGoogleApiClient()).createFolder(
getGoogleApiClient(), changeSet).setResultCallback(createFolderCallback);
}
final ResultCallback<DriveFolderResult> createFolderCallback = new ResultCallback<DriveFolderResult>() {
#Override
public void onResult(DriveFolderResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Error while trying to create the folder");
return;
}
showMessage("Created a folder: " + result.getDriveFolder().getDriveId());
}
};
private String folderName;
protected void createFolderInFolder(String exsistingDriveId, String folderName){
Drive.DriveApi.fetchDriveId(getGoogleApiClient(), exsistingDriveId).setResultCallback(idCallback);
this.folderName=folderName;
}
final ResultCallback<DriveIdResult> idCallback = new ResultCallback<DriveIdResult>() {
#Override
public void onResult(DriveIdResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Cannot find DriveId. Are you authorized to view this file?");
return;
}
DriveFolder folder = Drive.DriveApi
.getFolder(getGoogleApiClient(), result.getDriveId());
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(folderName).build();
folder.createFolder(getGoogleApiClient(), changeSet)
.setResultCallback(createFolderCallback);
}
};
private DriveId mFolderDriveId;
protected void createFileInFolder(String exsistingDriveId, String folderName){
Drive.DriveApi.fetchDriveId(getGoogleApiClient(), exsistingDriveId).setResultCallback(fileIdCallback);
this.folderName=folderName;
}
final private ResultCallback<DriveIdResult> fileIdCallback = new ResultCallback<DriveIdResult>() {
#Override
public void onResult(DriveIdResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Cannot find DriveId. Are you authorized to view this file?");
return;
}
mFolderDriveId = result.getDriveId();
Drive.DriveApi.newDriveContents(getGoogleApiClient())
.setResultCallback(driveContentsCallback);
}
};
final private ResultCallback<DriveContentsResult> driveContentsCallback =
new ResultCallback<DriveContentsResult>() {
#Override
public void onResult(DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Error while trying to create new file contents");
return;
}
DriveFolder folder = Drive.DriveApi.getFolder(getGoogleApiClient(), mFolderDriveId);
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(folderName)
.setMimeType("text/plain")
.setStarred(true).build();
folder.createFile(getGoogleApiClient(), changeSet, result.getDriveContents())
.setResultCallback(fileCallback);
}
};
final private ResultCallback<DriveFileResult> fileCallback =
new ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Error while trying to create the file");
return;
}
showMessage("Created a file: " + result.getDriveFile().getDriveId());
}
};
EDIT:
I have found the following:
if createFolderInFolder (or any other of the api-funcions) is called, just before an intent is used to call an activity, the result-callback will not be called while the called activity is running. When the main activity is receiving control, the onResume is called, and giving an exception, since connect is called again. Then the result-callback is never called.
This means that the api-call and the result-callback must be able to run without any other calls being run.
New Edit:
Step1: You need a callback function in your Fragment
private Callbacks mCallbacks;
interface Callbacks {
public void onSaveToDrive(byte[] filecontent);
}
Step2: The main activity must implement the fragment callback-methods
Step3: Call the api-function, and add the fragment-update call in the result-callback
If you would like to store the driveid, you should use DriveId.encodeFromString(exsistingDriveId) and DriveId.decodeFromString(exsistingDriveId) in this way you can store the existingDriveId, and safely refer to it again at a later stage, even if the drive has been disconnected and reconnected.
Remember one api-call must be done without calling other classes, since that may cause the onResume() to be called.
Related
I made an Android APP, which has an config structure, which contains data,and services I need throughout all activities.
I now face the issue, that if my APP is in the background for a while, my APP crash, because my config structure has been deleted.
In my config structure, I also have data, I can not easily recreate at runtime.
So in my first Activity, I create the config structure.
FreightWeightConfig config = new FreightWeightConfig(getApplicationContext()); // make sure our config is up and running
And the start of my config class looks like
public FreightWeightConfig(Context appContext) {
instance = this;
mApplicationContext = appContext;
tcBlue.setCallingContext(appContext);
tcBlueConfig = Config.getInstance(); // to make sure it is available straight away
mFirebaseAuth = FirebaseAuth.getInstance();
....
}
I have a second function in the config structure, which allows me to get the instance of my config class, which I need to get access to functions and interfaces in config and services.
public static synchronized FreightWeightConfig getInstance () {
//if (FreightWeightConfig.instance == null) {
// FreightWeightConfig.instance = new FreightWeightConfig(getApplication().getApplicationContext());
//}
if (FreightWeightConfig.instance == null){
FirebaseCrash.logcat(Log.ERROR, LOG_TAG, "Fatal Error : FreightWeightConfig.getInstance()==null. Try restarting the APP");
FirebaseCrash.logcat(Log.ERROR, LOG_TAG, "Fatal Error : Killing ourself, as we have no chance to go on");
//System.exit(0); // we are in a bad state
// Toast.makeText(mApplicationContext, "Fatal Error : FreightWeightConfig.getInstance()==null. Try restarting the APP", Toast.LENGTH_SHORT).show();
}
return FreightWeightConfig.instance;
}
In every Activity, I created a variable that hold a copy the instance. This is simply, because I thought it tells the system, I still need this class, do not kill it. Which does not seem to work.
I first thought, whenever I find my config class to be dead, I can recreate it. But it is not a simple task, as I need the APP context and need to recreate my services in the background. Also I store selections made, while navigating my APP
Anyone has a good Idea, how to solve the unloading / deleting my config class?
Based on the suggestion I extended Application like this:
public class FreightWeightApp extends Application implements DialogInterface.OnCancelListener {
private String LOG_TAG = "FreightWeightApp";
private FreightWeightConfig config;
public static int GOOGLE_PLAY_SERVIE_ABBORTED = 1001;
public void onCreate() {
super.onCreate();
// We first check if the google services are present, if not, better abort!!
int result = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this);
switch (result) {
case SUCCESS:
Log.d(LOG_TAG, "Google Services available");
break;
case SERVICE_MISSING:
Log.e(LOG_TAG, "Google Services missing, STOP");
googleServiceNotUpToDateDialog(result);
break;
case SERVICE_VERSION_UPDATE_REQUIRED:
Log.w(LOG_TAG, "Service update required");
googleServiceNotUpToDateDialog(result);
break;
case SERVICE_DISABLED:
Log.e(LOG_TAG, "Service disabled, STOP");
googleServiceNotUpToDateDialog(result);
break;
}
config = new FreightWeightConfig(getApplicationContext()); // make sure our config is up and running
}
private void googleServiceNotUpToDateDialog(int result) {
// Try to ask the user to update or finish off
// GoogleApiAvailability gaa = GoogleApiAvailability.getInstance();
// Dialog dialog = gaa.getErrorDialog(this, result, GOOGLE_PLAY_SERVIE_ABBORTED, this); //<==== Can not call this, as I have no Activity Context
// dialog.show();
}
#Override
public void onCancel(DialogInterface dialogInterface) {
// Now I should abbort the APP, or we will crash.
}
}
But now I have issues with verifying the GoogleService.
Dialog dialog = gaa.getErrorDialog(this, result, GOOGLE_PLAY_SERVIE_ABBORTED, this);
as I have no Activity Context.
Create your own implementation of Application, then initialize your config object in the onCreate() method(in this case the getInstance() method could also initialize the object).
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
FreightWeightConfig config = FreightWeightConfig.getInstance(getApplicationContext());
}
}
Declare your implementation in your app's module manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="your.package">
<application
android:name="your.package.MyApplication"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme">
...
</application>
</manifest>
Now you can use your config instance in the activities just like you do, calling getInstance(); the Application onCreate will do the load work. But there is no way to "unload the resources before the app process gets killed/cached", you just have to understand how Android components' lifecycle works.
I have a simple Android app. The main activity looks for a file on the internal device storage. What happens after that depends on whether the file exists or not. This sequence works just fine as the whole execution is synchronous.
If I now use Google Drive API, all file access are asynchronous and the main activity goes on without waiting for the result of my search...
I found no way to 'force' a synchronous behavior. Synchronous calls of Google API are not allowed in the UI thread.
Moreover, performing such calls in an AsyncTask leads to the same problem...
Any idea on how to handle such scenarios ?
Regards,
Laurent
------------------------- EDIT ----------------------------------
I tried several options but they all lead to the same result.
Both the main activity and the asynctask used to look for my file get blocked.
Any idea why and what to do ?
Main activity code :
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed) {
Log.i(mTag, "Terms of use agreement needs to be checked");
mCountDownLatch = new CountDownLatch(1);
TermsOfUseAgreementSearchTask searchAgreementTask = new TermsOfUseAgreementHandler().new TermsOfUseAgreementSearchTask();
// Start the async task to look for agreement file using parallel execution
searchAgreementTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
try {
// Block the main thread so that the task gets a chance to perform its Google Drive access...
mCountDownLatch.await(); // MAIN ACTIVITY HANGS HERE !
} catch (InterruptedException e) {
Log.e(mTag, "Failed to look for user agreement file", e);
}
// The async task should have finished (and decreased mCountDownLatch so that await returns)
// At this moment, the result should be available for the main activity in mTermsOfUseAgreed...
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed)
TermsOfUseAgreementHandler.checkTermsOfUse(this);
}
AsyncTask code :
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(MainActivity.mGoogleApiClient).queryChildren(MainActivity.mGoogleApiClient, lookForAgreement).await().getMetadataBuffer(); // ASYNCTASK HANGS HERE !
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
}
------------------------- EDIT 2 ----------------------------------
I also tried to move the connection to the drive API from the main activity to the asynctask as shown in a drive API example link.
I still get the same result but the problem is now the connection because the associated callback listener is never called...
public class TermsOfUseAgreementSearchTask extends AsyncTask<Void, Void, Boolean> {
private GoogleApiClient mClient = null;
final private CountDownLatch latch = new CountDownLatch(1);
public TermsOfUseAgreementSearchTask(Context context) {
GoogleApiClient.Builder builder = new GoogleApiClient.Builder(context)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addScope(Drive.SCOPE_APPFOLDER);
mClient = builder.build();
mClient.registerConnectionCallbacks(new ConnectionCallbacks() {
#Override
public void onConnected(Bundle arg0) {
Log.i(MainActivity.mTag, "Connected");
latch.countDown();
}
#Override
public void onConnectionSuspended(int arg0) {}
});
mClient.registerConnectionFailedListener(new OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.i(MainActivity.mTag, "Connection failed");
latch.countDown();
}
});
}
#Override
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Connect to drive API");
mClient.connect();
try {
Log.i(MainActivity.mTag, "Wait for connection");
latch.await(); // ASYNC TASK HANGS HERE !
} catch (InterruptedException e) {
return false;
}
if (!mClient.isConnected()) {
return false;
}
try {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(mClient).queryChildren(mClient, lookForAgreement).await().getMetadataBuffer();
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
//MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
} finally {
mClient.disconnect();
}
}
}
I'm following the same steps described here (the Google Fit client connection part is working fine).
final DataType dataType=TYPE_STEP_COUNT_DELTA;
DataSourcesRequest requestData = new DataSourcesRequest.Builder()
.setDataTypes(dataType) // At least one datatype must be specified.
.build();
Fitness.SensorsApi.findDataSources(mClient, requestData)
.setResultCallback(new ResultCallback<DataSourcesResult>() {
#Override
public void onResult(DataSourcesResult dataSourcesResult) {
Log.i(TAG, "Result: " + dataSourcesResult.getDataSources().size() + " sources "
+ dataSourcesResult.getStatus().toString());
for (DataSource dataSource : dataSourcesResult.getDataSources()) {
Log.i(TAG, "Data source found: " + dataSource.toString());
Log.i(TAG, "Data Source type: " + dataSource.getDataType().getName());
}
}
});
When I ask for data sources I get only one result which is the smartphone. If I add a listener then I really get data so it's working.
However it is also connected to an Android Wear smartwatch Gear Live with Android Wear app on the phone. Google Fit is installed in both of them but I'd like to get data from the smartwatch.
In the official guide I read
The Sensors API provides access to raw sensor data streams from
sensors available on the Android device and from sensors available in
companion devices, such as wearables.
This code is running on the smartphone so I think it would be right to expect data sources from companion smartwatch too. But it's like invisible to my phone application. Am I doing something wrong?
EDIT:
public class MainActivity extends AppCompatActivity {
private final static String TAG = "main_mobile";
private static final int REQUEST_OAUTH = 1;
private final static String DATE_FORMAT = "yyyy.MM.dd HH:mm:ss";
private static final String AUTH_PENDING = "auth_state_pending";
private boolean authInProgress = false;
private GoogleApiClient mClient = null;
private final static DataType dataType = TYPE_STEP_COUNT_DELTA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
authInProgress = savedInstanceState.getBoolean(AUTH_PENDING);
}
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.SENSORS_API)
.addApi(Fitness.RECORDING_API)
.addApi(Fitness.HISTORY_API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addConnectionCallbacks(connectionCallbacks)
.addOnConnectionFailedListener(connectionFailCallbacks)
.build();
}
private void initFitness() {
DataSourcesRequest requestData = new DataSourcesRequest.Builder()
.setDataTypes(dataType)
.build();
Fitness.SensorsApi.findDataSources(mClient, requestData)
.setResultCallback(new ResultCallback<DataSourcesResult>() {
#Override
public void onResult(DataSourcesResult dataSourcesResult) {
Log.i(TAG, "Result: " + dataSourcesResult.getDataSources().size() + " sources " + dataSourcesResult.getStatus().toString());
for (DataSource dataSource : dataSourcesResult.getDataSources()) {
Log.i(TAG, "\nData source found: \n\t" + dataSource.toString() + "\n\tType: " + dataSource.getDataType().getName());
}
}
});
}
#Override
protected void onStart() {
super.onStart();
Log.i(TAG, "Connecting...");
mClient.connect();
}
#Override
protected void onStop() {
super.onStop();
if (mClient.isConnected()) {
mClient.disconnect();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(AUTH_PENDING, authInProgress);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_OAUTH) {
authInProgress = false;
if (resultCode == RESULT_OK) {
// Make sure the app is not already connected or attempting to connect
if (!mClient.isConnecting() && !mClient.isConnected()) {
mClient.connect();
}
}
}
}
GoogleApiClient.ConnectionCallbacks connectionCallbacks = new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected!!!");
// Now you can make calls to the Fitness APIs.
// Put application specific code here.
initFitness();
}
#Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "Connection lost. Reason: Service Disconnected");
}
}
};
GoogleApiClient.OnConnectionFailedListener connectionFailCallbacks = new GoogleApiClient.OnConnectionFailedListener() {
// Called whenever the API client fails to connect.
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "Connection failed. Cause: " + result.toString());
if (!result.hasResolution()) {
// Show the localized error dialog
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), MainActivity.this, 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization dialog is displayed to the user.
if (!authInProgress) {
try {
Log.i(TAG, "Attempting to resolve failed connection");
authInProgress = true;
result.startResolutionForResult(MainActivity.this, REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Exception while starting resolution activity", e);
}
}
}
};
}
I have not tried any of this.
It seems as though the Samsung Gear Live Sensors are not supported out of the box, but you might be able to make it work via software sensors:
Your Gear Live
As said in this SO answer,
The Samsung Gear Live watch does not advertise itself as a BLE heart
rate monitor and therefore does not make the heart rate data
available via the normal Bluetooth Low Energy API or the Google
Fit API which is built upon it.
Supported Sensors
As said in the official docs,
Google Fit includes support for sensors on the mobile device and
Bluetooth Low Energy sensors paired with the device. Google Fit lets
developers implement support for other sensors and expose them as
software sensors in Android apps. Sensors supported by Google Fit are
available to Android apps as data source objects.
Possible Solution
It seems possible to implement additional software sensors.
(Copied template for this is at the bottom of the post, because it is lengthy).
You would get the data on the wearable following get-heart-rate-from-sensor-samsung-gear-live.
Template (from https://developers.google.com/fit/android/new-sensors)
Add this to your manifest file:
<service android:name="com.example.MySensorService"
android:process=":sensor">
<intent-filter>
<action android:name="com.google.android.gms.fitness.service.FitnessSensorService"/>
<!-- include at least one mimeType filter for the supported data types -->
<data android:mimeType="vnd.google.fitness.data_type/com.google.heart_rate.bpm"/>
</intent-filter>
</service>
and flesh this Service out:
import com.google.android.gms.common.*;
import com.google.android.gms.common.api.*;
import com.google.android.gms.fitness.*;
import com.google.android.gms.fitness.data.*;
import com.google.android.gms.fitness.service.*;
...
public class MySensorService extends FitnessSensorService {
#Override
public void onCreate() {
super.onCreate();
// 1. Initialize your software sensor(s).
// 2. Create DataSource representations of your software sensor(s).
// 3. Initialize some data structure to keep track of a registration for each sensor.
}
#Override
protected List<DataSource> onFindDataSources(List<DataType> dataTypes) {
// 1. Find which of your software sensors provide the data types requested.
// 2. Return those as a list of DataSource objects.
}
#Override
protected boolean onRegister(FitnessSensorServiceRequest request) {
// 1. Determine which sensor to register with request.getDataSource().
// 2. If a registration for this sensor already exists, replace it with this one.
// 3. Keep (or update) a reference to the request object.
// 4. Configure your sensor according to the request parameters.
// 5. When the sensor has new data, deliver it to the platform by calling
// request.getDispatcher().publish(List<DataPoint> dataPoints)
}
#Override
protected boolean onUnregister(DataSource dataSource) {
// 1. Configure this sensor to stop delivering data to the platform
// 2. Discard the reference to the registration request object
}
}
I am trying to implement Google Tag Manager by following the tutorial on this web page:
https://developers.google.com/tag-manager/android/v4/#init
But i cant seem to find what the ContainerLoadedCallback should do or where the source of this class is located. any help is greatly appreciated
What i currently have in my splash screen activity is the following:
PendingResult<ContainerHolder> pending =
App.getTagManager().loadContainerPreferNonDefault(C.Const.GTM_CONTAINER_ID,
R.raw.gtm_default_container);
// The onResult method will be called as soon as one of the following happens:
// 1. a saved container is loaded
// 2. if there is no saved container, a network container is loaded
// 3. the request times out. The example below uses a constant to manage the timeout period.
// The onResult method will be called as soon as one of the following happens:
// 1. a saved container is loaded
// 2. if there is no saved container, a network container is loaded
// 3. the request times out. The example below uses a constant to manage the timeout period.
pending.setResultCallback(new ResultCallback<ContainerHolder>() {
#Override
public void onResult(ContainerHolder containerHolder) {
ContainerHolderSingleton.setContainerHolder(containerHolder);
Container container = containerHolder.getContainer();
if (!containerHolder.getStatus().isSuccess()) {
Log.e("CuteAnimals", "failure loading container");
return;
}
ContainerHolderSingleton.setContainerHolder(containerHolder);
ContainerLoadedCallback.registerCallbacksForContainer(container);
containerHolder.setContainerAvailableListener(new ContainerHolder.ContainerAvailableListener() {
});
startMainActivity();
}
}, 2, TimeUnit.SECONDS);
It's implemented in their cuteanimals sample, you can find it in the sdk folder (assuming you have Google play services installed)
<SDK Dir>/extras/google/google_play_services/samples/tagmanager/cuteanimals/.../cuteanimals/SplashScreenActivity.java
Snippet:
private static class ContainerLoadedCallback implements ContainerHolder.ContainerAvailableListener {
#Override
public void onContainerAvailable(ContainerHolder containerHolder, String containerVersion) {
// We load each container when it becomes available.
Container container = containerHolder.getContainer();
registerCallbacksForContainer(container);
}
public static void registerCallbacksForContainer(Container container) {
// Register two custom function call macros to the container.
container.registerFunctionCallMacroCallback("increment", new CustomMacroCallback());
container.registerFunctionCallMacroCallback("mod", new CustomMacroCallback());
// Register a custom function call tag to the container.
container.registerFunctionCallTagCallback("custom_tag", new CustomTagCallback());
}
}
private static class CustomMacroCallback implements FunctionCallMacroCallback {
private int numCalls;
#Override
public Object getValue(String name, Map<String, Object> parameters) {
if ("increment".equals(name)) {
return ++numCalls;
} else if ("mod".equals(name)) {
return (Long) parameters.get("key1") % Integer.valueOf((String) parameters.get("key2"));
} else {
throw new IllegalArgumentException("Custom macro name: " + name + " is not supported.");
}
}
}
private static class CustomTagCallback implements FunctionCallTagCallback {
#Override
public void execute(String tagName, Map<String, Object> parameters) {
// The code for firing this custom tag.
Log.i("CuteAnimals", "Custom function call tag :" + tagName + " is fired.");
}
}
I'm trying to run some Google Drive sample code from the Android Developers website but I'm getting a number of instances where Android Studio cannot resolve method. I think most of the problem might be attributed to BaseDemoActivity also not being recognized. I've gone through the setup process to make sure I have everything correct and as far as I can see it is, I even just this morning updated my Google Play Services version in case it was that but still nothing. Can someone point me in the right direction of how I might fix this?
/**
* An activity to illustrate how to create a new folder.
*/
public class CreateFolderActivity extends BaseDemoActivity {
#Override
public void onConnected(Bundle connectionHint) {
super.onConnected(connectionHint);
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle("New folder").build();
Drive.DriveApi.getRootFolder(getGoogleApiClient()).createFolder(
getGoogleApiClient(), changeSet).addResultCallback(folderCreatedCallback);
}
ResultCallback<DriveFolderResult> folderCreatedCallback = new
ResultCallback<DriveFolderResult>() {
#Override
public void onResult(DriveFolderResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Error while trying to create the folder");
return;
}
showMessage("Created a folder: " + result.getDriveFolder().getDriveId());
}
}
}
BaseDemoActivity is not part of the API, its just used in the samples. Make sure you have that class copied into your app from the sample.