onsdag 4 februari 2015

Android : Implementing DRIVE API in your Android app ( Part 2 of 2 )

Lets implement Google Drive in a small sample app, the app will let the user input some text in an EditText and then send it to the Drive as a .txt file.

First of we need to sign the app before we can authorize it to the Google Drive API, we also need a AVD which incorporates Google APIs or a real device.

I'm building this app with Gradle (as i think most of Android developers now use.).

First we'll configure build.gradle
android {
    ...

    signingConfigs {
        release {
            storeFile file("D:\\EXAMPLE_KEYSTORE\\my_keystore")
            storePassword "myPassword"
            keyAlias "myKeyAlias"
            keyPassword "myPassword"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }

    ...
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'
    compile 'com.google.android.gms:play-services:6.5.87'
}


I've just included the updated parts, we need the new dependency for the Google Drive API and i've added a signingConfig so we can easily compile and sign the app with the key.

Using IntelliJ IDEA open the Gradle tab to the far right and double click 'installRelase'  it'll automatically install it on the emulator or a real device depending on what's connected to the ADB.


Back to the app, lets design our simple layout.
layout/main_activity.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">

    <TextView
            android:text="Message :"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    <EditText android:id="@+id/et_message"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:lines="5"/>

    <Button android:id="@+id/btn_send"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Send Text to Drive"
            />
</LinearLayout>

Nothing strange here, a barebone layout to enter our message and a button to send it. : )

MainActivity.java
package se.adanware.drive_example;

import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;


public class MainActivity extends ActionBarActivity {

    final static String MESSAGE = "Message";
    Button buttonSend;
    EditText edittextMessage;

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // Need to pass the requestcode & resultcode back to the fragment.
        Fragment fragment = getFragmentManager().findFragmentByTag("DRIVE_FRAGMENT");
        fragment.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        edittextMessage = (EditText) findViewById(R.id.et_message);
        buttonSend = (Button) findViewById(R.id.btn_send);
        buttonSend.setOnClickListener(myListener);
    }


    View.OnClickListener myListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            DriveWorkerFragment fragment = new DriveWorkerFragment();
            Bundle bundle = new Bundle();
            bundle.putString(MESSAGE, getMessage());
            fragment.setArguments(bundle);
            ft.add(fragment, "DRIVE_FRAGMENT").commit();
        }
    };

    private String getMessage()
    {
        return edittextMessage.getText().toString();
    }

}

Our MainActivity will just send the message to the DriveWorkerFragment. Notice the onActivityResult, need to pass it on to the fragment if the ConnectionResult has an resolution, i.e lets say it's the first time a user starts the app and we'll need to pick a Google account to use. This is just a barebone activity, preferably we'd have a progress dialog or something that shows that DriveWorkerFragment is doing it's job, so we dont cancel the process if the connection is slow.

DriveWorkerFragment.java

import java.io.IOException;
import java.io.OutputStream;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.drive.Drive;
import com.google.android.gms.drive.DriveApi.DriveContentsResult;
import com.google.android.gms.drive.DriveFolder;
import com.google.android.gms.drive.MetadataChangeSet;


public class DriveWorkerFragment extends Fragment implements ConnectionCallbacks, OnConnectionFailedListener
{
    // Just a key for retrieving the message.
    final static String MESSAGE = "Message";
    final static String TAG = "DriveExample";
    private String filename = "MyMessage.txt";
    private String messageToSend;
    private boolean isResolutionRunning = false;

    private static final int REQUEST_CODE_FOR_RESOLUTION = 10;
    private GoogleApiClient mGoogleApiClient;

     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         // See if we get RESULT_OK from resolution then connect again.
         isResolutionRunning = false;
         if(requestCode == REQUEST_CODE_FOR_RESOLUTION && resultCode == Activity.RESULT_OK) {
              mGoogleApiClient.connect();

         }

     }

     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Bundle bundle = getArguments();
         if(bundle != null)
             messageToSend = bundle.getString(MESSAGE);
     }

     @Override
    public void onResume() {
         super.onResume();
         if (!isResolutionRunning) {
             if (mGoogleApiClient == null) {
                 mGoogleApiClient = new GoogleApiClient.Builder(getActivity().getBaseContext())
                         .addApi(Drive.API)
                         .addScope(Drive.SCOPE_FILE)
                         .addConnectionCallbacks(this)
                         .addOnConnectionFailedListener(this)
                         .build();
                 mGoogleApiClient.connect();
             }
         }
    }

     final private ResultCallback<DriveContentsResult> contentsCallback = new
             ResultCallback<DriveContentsResult>() {
                 @Override
                 public void onResult(DriveContentsResult result) {
                     if (!result.getStatus().isSuccess()) {
                         Toast.makeText(getActivity().getBaseContext(), "Error.", Toast.LENGTH_LONG).show();
                         return;
                     }

                     
                     OutputStream outputStream = result.getDriveContents().getOutputStream();

                     try { // Message to byte[]
                         outputStream.write(messageToSend.getBytes());
                         outputStream.close();
                     } catch (IOException e1) {
                         Log.i(TAG, "Unable to write file contents.");
                     }

                     MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                             .setTitle(filename)
                             .setMimeType("text/plain")
                             .setStarred(true).build();
                     // create a file on root folder
                     Drive.DriveApi.getRootFolder(mGoogleApiClient)
                             .createFile(mGoogleApiClient, changeSet, result.getDriveContents())
                             .setResultCallback(resultCallback);
                 }
             };

     @Override
    public void onPause() {
        if (mGoogleApiClient != null) {
            mGoogleApiClient.disconnect();
        }
        super.onPause();
    }


    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // Called whenever the API client fails to connect.
        Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
        if (result.hasResolution()) {
            try
            {
                isResolutionRunning = true;
                result.startResolutionForResult(this.getActivity(), REQUEST_CODE_FOR_RESOLUTION);

            }
            catch (Exception e)
            {
                Log.d(TAG, e.toString());
            }
        }
        else
        { // No resolution found, show error message.
            GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), getActivity(), 0).show();
            return;
        }

    }

     @Override
     public void onConnected(Bundle connectionHint) {
         // create new contents resource
         Drive.DriveApi.newDriveContents(mGoogleApiClient)
                 .setResultCallback(contentsCallback);
     }

    @Override
    public void onConnectionSuspended(int i) {
        //
    }

    final private ResultCallback<DriveFolder.DriveFileResult> resultCallback = new
             ResultCallback<DriveFolder.DriveFileResult>() {
                 @Override
                 public void onResult(DriveFolder.DriveFileResult result) {
                     if (!result.getStatus().isSuccess()) {
                         // Show error message.
                         return;
                     }
                     Toast.makeText(getActivity().getBaseContext(), "Textfile sent!", Toast.LENGTH_LONG).show();
                     // Remove fragment.
                     Fragment fragment = getFragmentManager().findFragmentByTag("DRIVE_FRAGMENT");
                     if(fragment != null) {
                         getFragmentManager().beginTransaction().remove(fragment).commit();
                     }
                 }
             };
}



That's it, if you've created a Client ID on Google Developers Console with the correct key and package name you can now run it to send a message to your Drive! Hope it helps!


Inga kommentarer:

Skicka en kommentar