반응형




Google Calendar에 캘린더를 생성하고 이벤트를 추가 및  이벤트 리스트를 가져오는 예제코드 입니다. 



1. Google Calendar API 사용 설정


2. AndroidManifest.xml


3. build.gradle


4. activity_main.xml


5. MainActivity.java


참고




2018.   7. 30 - 최초 작성


2018. 10. 10 - 인증 관련 버그 수정


Attempt to invoke virtual method 'java.util.List com.google.api.services.calendar.model.CalendarList.getItmes()' on a null object reference


            try {

                calendarList = mService.calendarList().list().setPageToken(pageToken).execute();

            } catch (UserRecoverableAuthIOException e) {  // 추가

                startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);

            }catch (IOException e) {

                e.printStackTrace();

            }


2019. 8. 8 - Google Developers Console 사이트에서 OAuth 2.0 관련 변경사항 반영

                   androidx 사용




우선 실행 결과입니다.


캘린더와 연동 테스트를 진행합니다. 

ADD CALENDAR,  ADD EVENT, GET EVENT 순으로 버튼을 선택하여 진행했습니다.


ADD CALENDAR를 선택해봅니다. 





주소록 사용에 대한 런타임 퍼미션을 물어봅니다.  허용을 선택합니다. 

(다른 버튼을 먼저 클릭했다면 똑같이 런타임 퍼미션을 물어봅니다. )





캘린더를 연결할 구글 계정을 선택합니다. 





순간적으로 앱에서 에러난 게 보이면서 아래 화면으로 넘어옵니다.

(앱에서 에러 로그 출력을 안하도록 하면 해결될듯합니다. )

허용을 선택합니다. 





구글 캘린더에 캘린더를 추가했습니다. 





웹용 구글 캘린더에서 확인해보면 CalendarTitle이 추가되어 있습니다. 





이제  ADD Event를 선택하여 이벤트를 캘린더에  추가합니다.





GET EVENT를 클릭하면 캘린더에서 가져온 이벤트를 보여줍니다. 

(아래는 예전 스크린샷을 그대로 사용했습니다.)






구글 캘린더 API를 사용하기 위한 준비부터 시작합니다.

1. Google Calendar API 사용 설정

1-1. Google Developers Console 사이트 (https://console.developers.google.com/apis/dashboard )에 접속하여 프로젝트 만들기를 클릭합니다.





1-2. 프로젝트 이름을 입력하고 만들기를 클릭합니다. 





1-3. 왼쪽에 보이는 메뉴에서 라이브러리를 클릭합니다.





1-4. 검색창에 calendar를 입력하면 보이는 Google Calendar API를 클릭합니다. 





1-5. 사용 설정을 클릭합니다. 





1-6. Google Calendar API가 활성화 되었습니다. 이제 인증 정보를 추가해야 합니다. 

사용자 인증 정보 만들기를 클릭합니다. 





1-7. 어떤 API를 사용 중이신가요?에 Google Calendar API,  API를 호출할 위치로 Android, 액세스할 데이터로 사용자 데이터를 선택하고 어떤 사용자 인증 정보가 필요한가요?를 클릭합니다. 





1-8. 동의 화면 설정을 클릭합니다. 





1-9. 새로운 창에서 진행됩니다. 애플리케이션 이름을 입력하고 아래로 스크롤하면 보이는 저장을 선택합니다. 




1-10. 1-9에서 새로운 창에서 진행되었다면 아래처럼 화면이 보이는 현재창을 닫습니다. 





1-11. 기존 창에서 새로고침을 선택합니다. 





1-12. 이제 창이 다음처럼 보입니다. 





1-13. 진행하기 위해서는 SHA1패키지 이름이 필요합니다.





먼저 명령 프롬프트에서 다음 명령을 실행하여 SHA1을 출력해둡니다.(빨간색 사각형 부분)


"C:\Program Files\Android\Android Studio\jre\bin\keytool" -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android




안드로이드 프로젝트(Empty Activity 선택)를 생성하여 패키지 이름을 확인해둡니다. 





1-14. 앞에서 확인해둔 SHA1 패키지 이름을 입력해주고 OAuth 클라이언트 ID 만들기를 클릭합니다. 





1-15. 완료를 클릭합니다. 



이제 구글 캘린더 API를 사용할 준비가 완료되었습니다. 

OAuth 2.0을 사용하기 때문에 인증을 위해 안드로이드 앱에 추가할 것은 없습니다. 




이제 앞에서 생성했던 프로젝트의 코드를 변경합니다. 


2. AndroidManifest.xml


매니페스트 파일에 다음 권한들을 추가해줍니다.



<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.webnautes.googlecalendarapiexample">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"



3. build.gradle


Google API Client Library를 안드로이드 프로젝트에 사용할 수 있도록 패키지를 추가합니다.



dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.media:media:1.0.1'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'com.google.android.gms:play-services-auth:17.0.0'
    implementation 'pub.devrel:easypermissions:0.3.0'
    implementation('com.google.api-client:google-api-client-android:1.22.0') {
        exclude group: 'org.apache.httpcomponents'
    }
    implementation('com.google.apis:google-api-services-calendar:v3-rev235-1.22.0') {
        exclude group: 'org.apache.httpcomponents'
    }
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}




4. activity_main.xml

기존 내용을 교체합니다.



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="16dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingTop="16dp"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textview_main_status"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="40"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:layout_marginEnd="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:text=""/>

    <TextView
        android:id="@+id/textview_main_result"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="80"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:layout_marginEnd="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:text=""/>

    <Button
        android:id="@+id/button_main_add_calendar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Add Calendar" />


    <Button
        android:id="@+id/button_main_add_event"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Add Event" />


    <Button
        android:id="@+id/button_main_get_event"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Get Event" />

</LinearLayout>




5. MainActivity.java

패키지 선언을 제외하고 모두 지운 후, 붙여넣기합니다.


import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;

import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.DateTime;
import com.google.api.client.util.ExponentialBackOff;

import com.google.api.services.calendar.CalendarScopes;

import com.google.api.services.calendar.model.*;

import android.Manifest;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;


public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {


    /**
    * Google Calendar API에 접근하기 위해 사용되는 구글 캘린더 API 서비스 객체
    */

    private com.google.api.services.calendar.Calendar mService = null;

    /**
    * Google Calendar API 호출 관련 메커니즘 및 AsyncTask을 재사용하기 위해 사용
    */
    private  int mID = 0;


    GoogleAccountCredential mCredential;
    private TextView mStatusText;
    private TextView mResultText;
    private Button mGetEventButton;
    private Button mAddEventButton;
    private Button mAddCalendarButton;
    ProgressDialog mProgress;


    static final int REQUEST_ACCOUNT_PICKER = 1000;
    static final int REQUEST_AUTHORIZATION = 1001;
    static final int REQUEST_GOOGLE_PLAY_SERVICES = 1002;
    static final int REQUEST_PERMISSION_GET_ACCOUNTS = 1003;


    private static final String PREF_ACCOUNT_NAME = "accountName";
    private static final String[] SCOPES = {CalendarScopes.CALENDAR};



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mAddCalendarButton = (Button) findViewById(R.id.button_main_add_calendar);
        mAddEventButton = (Button) findViewById(R.id.button_main_add_event);
        mGetEventButton = (Button) findViewById(R.id.button_main_get_event);

        mStatusText = (TextView) findViewById(R.id.textview_main_status);
        mResultText = (TextView) findViewById(R.id.textview_main_result);


        /**
        * 버튼 클릭으로 동작 테스트
        */
        mAddCalendarButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mAddCalendarButton.setEnabled(false);
                mStatusText.setText("");
                mID = 1;           //캘린더 생성
                getResultsFromApi();
                mAddCalendarButton.setEnabled(true);
            }
        });


        mAddEventButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mAddEventButton.setEnabled(false);
                mStatusText.setText("");
                mID = 2;        //이벤트 생성
                getResultsFromApi();
                mAddEventButton.setEnabled(true);
            }
        });


        mGetEventButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mGetEventButton.setEnabled(false);
                mStatusText.setText("");
                mID = 3;        //이벤트 가져오기
                getResultsFromApi();
                mGetEventButton.setEnabled(true);
            }
        });


        // Google Calendar API의 호출 결과를 표시하는 TextView를 준비
        mResultText.setVerticalScrollBarEnabled(true);
        mResultText.setMovementMethod(new ScrollingMovementMethod());

        mStatusText.setVerticalScrollBarEnabled(true);
        mStatusText.setMovementMethod(new ScrollingMovementMethod());
        mStatusText.setText("버튼을 눌러 테스트를 진행하세요.");


        // Google Calendar API 호출중에 표시되는 ProgressDialog
        mProgress = new ProgressDialog(this);
        mProgress.setMessage("Google Calendar API 호출 중입니다.");


        // Google Calendar API 사용하기 위해 필요한 인증 초기화( 자격 증명 credentials, 서비스 객체 )
        // OAuth 2.0를 사용하여 구글 계정 선택 및 인증하기 위한 준비
        mCredential = GoogleAccountCredential.usingOAuth2(
                getApplicationContext(),
                Arrays.asList(SCOPES)
        ).setBackOff(new ExponentialBackOff()); // I/O 예외 상황을 대비해서 백오프 정책 사용

    }




    /**
    * 다음 사전 조건을 모두 만족해야 Google Calendar API를 사용할 수 있다.
    *
    * 사전 조건
    * - Google Play Services 설치
    * - 유효한 구글 계정 선택
    * - 안드로이드 디바이스에서 인터넷 사용 가능
    *
    * 하나라도 만족하지 않으면 해당 사항을 사용자에게 알림.
    */
    private String getResultsFromApi() {

        if (!isGooglePlayServicesAvailable()) { // Google Play Services를 사용할 수 없는 경우

            acquireGooglePlayServices();
        } else if (mCredential.getSelectedAccountName() == null) { // 유효한 Google 계정이 선택되어 있지 않은 경우

            chooseAccount();
        } else if (!isDeviceOnline()) {    // 인터넷을 사용할 수 없는 경우

            mStatusText.setText("No network connection available.");
        } else {

            // Google Calendar API 호출
            new MakeRequestTask(this, mCredential).execute();
        }
        return null;
    }



    /**
    * 안드로이드 디바이스에 최신 버전의 Google Play Services가 설치되어 있는지 확인
    */
    private boolean isGooglePlayServicesAvailable() {

        GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();

        final int connectionStatusCode = apiAvailability.isGooglePlayServicesAvailable(this);
        return connectionStatusCode == ConnectionResult.SUCCESS;
    }



    /*
    * Google Play Services 업데이트로 해결가능하다면 사용자가 최신 버전으로 업데이트하도록 유도하기위해
    * 대화상자를 보여줌.
    */
    private void acquireGooglePlayServices() {

        GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
        final int connectionStatusCode = apiAvailability.isGooglePlayServicesAvailable(this);

        if (apiAvailability.isUserResolvableError(connectionStatusCode)) {

            showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode);
        }
    }



    /*
    * 안드로이드 디바이스에 Google Play Services가 설치 안되어 있거나 오래된 버전인 경우 보여주는 대화상자
    */
    void showGooglePlayServicesAvailabilityErrorDialog(
            final int connectionStatusCode
    ) {

        GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();

        Dialog dialog = apiAvailability.getErrorDialog(
                MainActivity.this,
                connectionStatusCode,
                REQUEST_GOOGLE_PLAY_SERVICES
        );
        dialog.show();
    }



    /*
    * Google Calendar API의 자격 증명( credentials ) 에 사용할 구글 계정을 설정한다.
    *
    * 전에 사용자가 구글 계정을 선택한 적이 없다면 다이얼로그에서 사용자를 선택하도록 한다.
    * GET_ACCOUNTS 퍼미션이 필요하다.
    */
    @AfterPermissionGranted(REQUEST_PERMISSION_GET_ACCOUNTS)
    private void chooseAccount() {

        // GET_ACCOUNTS 권한을 가지고 있다면
        if (EasyPermissions.hasPermissions(this, Manifest.permission.GET_ACCOUNTS)) {


            // SharedPreferences에서 저장된 Google 계정 이름을 가져온다.
            String accountName = getPreferences(Context.MODE_PRIVATE)
                    .getString(PREF_ACCOUNT_NAME, null);
            if (accountName != null) {

                // 선택된 구글 계정 이름으로 설정한다.
                mCredential.setSelectedAccountName(accountName);
                getResultsFromApi();
            } else {


                // 사용자가 구글 계정을 선택할 수 있는 다이얼로그를 보여준다.
                startActivityForResult(
                        mCredential.newChooseAccountIntent(),
                        REQUEST_ACCOUNT_PICKER);
            }



            // GET_ACCOUNTS 권한을 가지고 있지 않다면
        } else {


            // 사용자에게 GET_ACCOUNTS 권한을 요구하는 다이얼로그를 보여준다.(주소록 권한 요청함)
            EasyPermissions.requestPermissions(
                    (Activity)this,
                    "This app needs to access your Google account (via Contacts).",
                    REQUEST_PERMISSION_GET_ACCOUNTS,
                    Manifest.permission.GET_ACCOUNTS);
        }
    }



    /*
    * 구글 플레이 서비스 업데이트 다이얼로그, 구글 계정 선택 다이얼로그, 인증 다이얼로그에서 되돌아올때 호출된다.
    */

    @Override
    protected void onActivityResult(
            int requestCode,  // onActivityResult가 호출되었을 때 요청 코드로 요청을 구분
            int resultCode,   // 요청에 대한 결과 코드
            Intent data
    ) {
        super.onActivityResult(requestCode, resultCode, data);


        switch (requestCode) {

            case REQUEST_GOOGLE_PLAY_SERVICES:

                if (resultCode != RESULT_OK) {

                    mStatusText.setText( " 앱을 실행시키려면 구글 플레이 서비스가 필요합니다."
                            + "구글 플레이 서비스를 설치 후 다시 실행하세요." );
                } else {

                    getResultsFromApi();
                }
                break;


            case REQUEST_ACCOUNT_PICKER:
                if (resultCode == RESULT_OK && data != null && data.getExtras() != null) {
                    String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                    if (accountName != null) {
                        SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
                        SharedPreferences.Editor editor = settings.edit();
                        editor.putString(PREF_ACCOUNT_NAME, accountName);
                        editor.apply();
                        mCredential.setSelectedAccountName(accountName);
                        getResultsFromApi();
                    }
                }
                break;


            case REQUEST_AUTHORIZATION:

                if (resultCode == RESULT_OK) {
                    getResultsFromApi();
                }
                break;
        }
    }


    /*
    * Android 6.0 (API 23) 이상에서 런타임 권한 요청시 결과를 리턴받음
    */
    @Override
    public void onRequestPermissionsResult(
            int requestCode,  //requestPermissions(android.app.Activity, String, int, String[])에서 전달된 요청 코드
            @NonNull String[] permissions, // 요청한 퍼미션
            @NonNull int[] grantResults    // 퍼미션 처리 결과. PERMISSION_GRANTED 또는 PERMISSION_DENIED
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }


    /*
    * EasyPermissions 라이브러리를 사용하여 요청한 권한을 사용자가 승인한 경우 호출된다.
    */
    @Override
    public void onPermissionsGranted(int requestCode, List<String> requestPermissionList) {

        // 아무일도 하지 않음
    }


    /*
    * EasyPermissions 라이브러리를 사용하여 요청한 권한을 사용자가 거부한 경우 호출된다.
    */
    @Override
    public void onPermissionsDenied(int requestCode, List<String> requestPermissionList) {

        // 아무일도 하지 않음
    }


    /*
    * 안드로이드 디바이스가 인터넷 연결되어 있는지 확인한다. 연결되어 있다면 True 리턴, 아니면 False 리턴
    */
    private boolean isDeviceOnline() {

        ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();

        return (networkInfo != null && networkInfo.isConnected());
    }


    /*
    * 캘린더 이름에 대응하는 캘린더 ID를 리턴
    */
    private String getCalendarID(String calendarTitle){

        String id = null;

        // Iterate through entries in calendar list
        String pageToken = null;
        do {
            CalendarList calendarList = null;
            try {
                calendarList = mService.calendarList().list().setPageToken(pageToken).execute();
            } catch (UserRecoverableAuthIOException e) {
                startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
            }catch (IOException e) {
                e.printStackTrace();
            }
            List<CalendarListEntry> items = calendarList.getItems();


            for (CalendarListEntry calendarListEntry : items) {

                if ( calendarListEntry.getSummary().toString().equals(calendarTitle)) {

                    id = calendarListEntry.getId().toString();
                }
            }
            pageToken = calendarList.getNextPageToken();
        } while (pageToken != null);

        return id;
    }


    /*
    * 비동기적으로 Google Calendar API 호출
    */
    private class MakeRequestTask extends AsyncTask<Void, Void, String> {

        private Exception mLastError = null;
        private MainActivity mActivity;
        List<String> eventStrings = new ArrayList<String>();


        public MakeRequestTask(MainActivity activity, GoogleAccountCredential credential) {

            mActivity = activity;

            HttpTransport transport = AndroidHttp.newCompatibleTransport();
            JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();

            mService = new com.google.api.services.calendar.Calendar
                    .Builder(transport, jsonFactory, credential)
                    .setApplicationName("Google Calendar API Android Quickstart")
                    .build();
        }


        @Override
        protected void onPreExecute() {
            // mStatusText.setText("");
            mProgress.show();
            mStatusText.setText("데이터 가져오는 중...");
            mResultText.setText("");
        }


        /*
        * 백그라운드에서 Google Calendar API 호출 처리
        */
        @Override
        protected String doInBackground(Void... params) {
            try {

                if ( mID == 1) {

                    return createCalendar();

                }else if (mID == 2) {

                    return addEvent();
                }
                else if (mID == 3) {

                    return getEvent();
                }


            } catch (Exception e) {
                mLastError = e;
                cancel(true);
                return null;
            }

            return null;
        }


        /*
        * CalendarTitle 이름의 캘린더에서 10개의 이벤트를 가져와 리턴
        */
        private String getEvent() throws IOException {


            DateTime now = new DateTime(System.currentTimeMillis());

            String calendarID = getCalendarID("CalendarTitle");
            if ( calendarID == null ){

                return "캘린더를 먼저 생성하세요.";
            }


            Events events = mService.events().list(calendarID)//"primary")
                    .setMaxResults(10)
                    //.setTimeMin(now)
                    .setOrderBy("startTime")
                    .setSingleEvents(true)
                    .execute();
            List<Event> items = events.getItems();


            for (Event event : items) {

                DateTime start = event.getStart().getDateTime();
                if (start == null) {

                    // 모든 이벤트가 시작 시간을 갖고 있지는 않다. 그런 경우 시작 날짜만 사용
                    start = event.getStart().getDate();
                }


                eventStrings.add(String.format("%s \n (%s)", event.getSummary(), start));
            }


            return eventStrings.size() + "개의 데이터를 가져왔습니다.";
        }

        /*
        * 선택되어 있는 Google 계정에 새 캘린더를 추가한다.
        */
        private String createCalendar() throws IOException {

            String ids = getCalendarID("CalendarTitle");

            if ( ids != null ){

                return "이미 캘린더가 생성되어 있습니다. ";
            }

            // 새로운 캘린더 생성
            com.google.api.services.calendar.model.Calendar calendar = new Calendar();

            // 캘린더의 제목 설정
            calendar.setSummary("CalendarTitle");


            // 캘린더의 시간대 설정
            calendar.setTimeZone("Asia/Seoul");

            // 구글 캘린더에 새로 만든 캘린더를 추가
            Calendar createdCalendar = mService.calendars().insert(calendar).execute();

            // 추가한 캘린더의 ID를 가져옴.
            String calendarId = createdCalendar.getId();


            // 구글 캘린더의 캘린더 목록에서 새로 만든 캘린더를 검색
            CalendarListEntry calendarListEntry = mService.calendarList().get(calendarId).execute();

            // 캘린더의 배경색을 파란색으로 표시  RGB
            calendarListEntry.setBackgroundColor("#0000ff");

            // 변경한 내용을 구글 캘린더에 반영
            CalendarListEntry updatedCalendarListEntry =
                    mService.calendarList()
                            .update(calendarListEntry.getId(), calendarListEntry)
                            .setColorRgbFormat(true)
                            .execute();

            // 새로 추가한 캘린더의 ID를 리턴
            return "캘린더가 생성되었습니다.";
        }


        @Override
        protected void onPostExecute(String output) {

            mProgress.hide();
            mStatusText.setText(output);

            if ( mID == 3 )   mResultText.setText(TextUtils.join("\n\n", eventStrings));
        }


        @Override
        protected void onCancelled() {
            mProgress.hide();
            if (mLastError != null) {
                if (mLastError instanceof GooglePlayServicesAvailabilityIOException) {
                    showGooglePlayServicesAvailabilityErrorDialog(
                            ((GooglePlayServicesAvailabilityIOException) mLastError)
                                    .getConnectionStatusCode());
                } else if (mLastError instanceof UserRecoverableAuthIOException) {
                    startActivityForResult(
                            ((UserRecoverableAuthIOException) mLastError).getIntent(),
                            MainActivity.REQUEST_AUTHORIZATION);
                } else {
                    mStatusText.setText("MakeRequestTask The following error occurred:\n" + mLastError.getMessage());
                }
            } else {
                mStatusText.setText("요청 취소됨.");
            }
        }


        private String addEvent() {


            String calendarID = getCalendarID("CalendarTitle");

            if ( calendarID == null ){

                return "캘린더를 먼저 생성하세요.";

            }

            Event event = new Event()
                    .setSummary("구글 캘린더 테스트")
                    .setLocation("서울시")
                    .setDescription("캘린더에 이벤트 추가하는 것을 테스트합니다.");


            java.util.Calendar calander;

            calander = java.util.Calendar.getInstance();
            SimpleDateFormat simpledateformat;
            //simpledateformat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ", Locale.KOREA);
            // Z에 대응하여 +0900이 입력되어 문제 생겨 수작업으로 입력
            simpledateformat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss+09:00", Locale.KOREA);
            String datetime = simpledateformat.format(calander.getTime());

            DateTime startDateTime = new DateTime(datetime);
            EventDateTime start = new EventDateTime()
                    .setDateTime(startDateTime)
                    .setTimeZone("Asia/Seoul");
            event.setStart(start);

            Log.d( "@@@", datetime );


            DateTime endDateTime = new  DateTime(datetime);
            EventDateTime end = new EventDateTime()
                    .setDateTime(endDateTime)
                    .setTimeZone("Asia/Seoul");
            event.setEnd(end);

            //String[] recurrence = new String[]{"RRULE:FREQ=DAILY;COUNT=2"};
            //event.setRecurrence(Arrays.asList(recurrence));


            try {
                event = mService.events().insert(calendarID, event).execute();
            } catch (Exception e) {
                e.printStackTrace();
                Log.e("Exception", "Exception : " + e.toString());
            }
            System.out.printf("Event created: %s\n", event.getHtmlLink());
            Log.e("Event", "created : " + event.getHtmlLink());
            String eventStrings = "created : " + event.getHtmlLink();
            return eventStrings;
        }
    }


}




참고


https://qiita.com/couzie/items/ce8f7780f9a722b2a87d


https://github.com/aksolutions/AndroidAddEventToGoogleCalendar 


https://github.com/miguelarauj1o/CalendarQuickStart


https://developers.google.com/calendar/v3/reference/




반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요.

  1. 안녕하세요 2018.10.10 15:00

    안녕하세요
    Attempt to invoke virtual method 'java.util.Listcom.google.api.services.calendar.model.CalendarList.getItmes()' on a null object reference
    라는 에러가 나는데 혹시 아실까요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.10.10 21:33 신고

      버그가 생겼네요.. 아래처럼 해주세요..

      try {
      calendarList = mService.calendarList().list().setPageToken(pageToken).execute();
      } catch (UserRecoverableAuthIOException e) { // 추가
      startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
      }catch (IOException e) {
      e.printStackTrace();
      }

  2. 안녕하십니까 2018.10.10 17:44

    안녕하십니까
    저도 다 그대로 하고 한거 같은데
    위에 분과 같은 에러가 나는데
    무슨 문제일까요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.10.10 21:33 신고

      버그가 생겼네요.. 아래처럼 해주세요..

      try {
      calendarList = mService.calendarList().list().setPageToken(pageToken).execute();
      } catch (UserRecoverableAuthIOException e) { // 추가
      startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
      }catch (IOException e) {
      e.printStackTrace();
      }

  3. 안녕하세요 2018.11.29 22:35

    정보 감사합니다
    올려주신 코드를 토대로 프래그먼트에 적용해보았는데요.
    문제 없이 add calendar 외 2개 버튼이 모두 뜨게(문제없이)잘 실행 됩니다.
    후에 버튼 3개중 하나를 누르면 구글 계정을 선택하라는 다이얼로그가 나오는데
    거기서 아이디를 선택하고 확인을 누르면 아무 반응이 없습니다.
    로그캣에 에러가 뜨는것도 아닙니다
    아이디 선택하고 확인을 하면 누르는 로그캣은
    I/ViewRootImpl: ViewRoot's Touch Event : ACTION_DOWN
    I/ViewRootImpl: ViewRoot's Touch Event : ACTION_UP
    I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy@182f29d time:24155024
    이 전부 입니다.
    무엇이 문제일까요.. 구글맵 연동했을때 인증이 제대로 되지 않으면 회색바탕이 뜨는것과 같은 이유일까요ㅠㅠ

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.11.29 22:40 신고

      코드에서 다음 부분이 실행안되는듯 합니다.

      startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);

  4. 안녕하세요 2018.11.29 22:52

    빠른 답글 정말 감사합니다.
    프래그먼트로 바꿔서 적용시킬때 언급해주신 부분에서 MainActivity.REQUEST_AUTHORIZATION가 빨간글씨가 되길래
    제가 사용하려는 프래그먼트 액티비티 이름.REQUEST_AUTHORIZATION로 바꿔서 사용중입니다.
    오류가 없길래 저대로 실행을 했는데 제가 너무 단순하게 생각한걸까요..

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.11.29 23:24 신고

      그부분이 아니라 여기입니다.

      try {
      calendarList = mService.calendarList().list().setPageToken(pageToken).execute();
      } catch (UserRecoverableAuthIOException e) { // 추가
      startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
      }catch (IOException e) {
      e.printStackTrace();
      }

  5. 안녕하세요 2018.11.29 23:35

    여기는 건든게 없는거 같은데....무튼 어디가 문제인지 알려주셔서 감사합니다
    혹시 어떤식으로 해결해야 하는지 알려주실 수 있나요?
    프래그먼트에서 startActivityForResult 를 사용해서 그런걸까요 다른 방식으로 알아봐야 할까봐요
    ++
    알려주신곳 찾아서 수정했더니 잘 작동 합니다!!!! 정말 감사합니다
    이제 달력 모양으로 바뀌는걸 찾아봐야겠어요
    정말 감사합니다

  6. 123 2018.12.04 22:50

    안녕하세요.
    MainActivity를 사용하지 않고
    기존 제가 가진 프로젝트에 새로운 액티비티(액티비티명은 CalendarActivity)를 추가해서 사용해보려고 했는데

    getResultsFromApi() 함수의
    // Google Calendar API 호출
    new MakeRequestTask(this, mCredential).execute(); 이 부분이 오류가 납니다.

    오류는 error: incompatible types: CalendarActivity cannot be converted to MainActivity 이렇게 뜹니다.

    그래서 new MakeRequestTask(CalendarActivity.this, mCredential).execute(); 로 변경해보아도 여전히 빨간줄이 생기네요ㅠㅠ
    getApplication(), getContextApplication() 등등으로 다 변경해 보았는데도 안됩니다..
    어떻게 해결해야 할지 아실까요?ㅠㅠ

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.12.04 22:58 신고

      다음처럼 정의된 부분때문에 생긴 문제 같습니다.

      public MakeRequestTask(MainActivity activity, GoogleAccountCredential credential)

      MainActivity 를 수정해보세요

    • 123 2018.12.04 23:29

      해결하였습니다 감사합니다!
      혹시 캘린더를 주소 텍스트 형식으로 말고, 실제로 캘린더 자체가 보여지도록 가져올 순 없나요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.12.04 23:32 신고

      UI는 따로 만들어야 하는 듯합니다.

  7. Hanieum 2019.07.08 16:44

    계속해서 Gradle 관련해서 계속해서 오류가 나는데 어떤게 문제인지 알 수 있을까요?? Gradle설정은 나오는대로 했는데요...
    구글 API나 구글 캘린더 API 둘다 똑같은 문제로 안되네요ㅠㅠ

    ERROR: Gradle DSL method not found: 'implementation()'
    Possible causes:
    The project 'Hanieum3-master' may be using a version of the Android Gradle plug-in that does not contain the method (e.g. 'testCompile' was added in 1.1.0).
    Upgrade plugin to version 3.4.1 and sync project

    The project 'Hanieum3-master' may be using a version of Gradle that does not contain the method.
    Gradle settings

    The build file may be missing a Gradle plugin.
    Apply Gradle plugin

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.08 17:17 신고

      플러그인을 3.4.1로 업그레이드하라는 메세지네요..
      다음 링크를 참고해보세요.

      https://stackoverflow.com/a/35272475

  8. 이민재 2019.07.16 22:42

    안녕하세요
    해당 구글 계정 들어간뒤 로그아웃하고 다른 계정으로는 못하는건가요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.16 23:19 신고

      찾아보니 다음과 같은 글이 보이는 것을 봐서는 가능한 듯합니다.

      https://stackoverflow.com/a/42257271

  9. 이민재 2019.07.17 19:33

    안녕하세요.
    혹시 일정을 추가할때 EditText로 해서 가능한지 여쭤보고 싶습니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.17 19:47 신고

      addEvent 메소드의 다음 부분을 EditText에서 가져온 걸로 채우면 될듯합니다.

      Event event = new Event()
      .setSummary("구글 캘린더 테스트")
      .setLocation("서울시")
      .setDescription("캘린더에 이벤트 추가하는 것을 테스트합니다.");

  10. 이민재 2019.07.17 19:58

    공기계로 빌드해서 하고있는데 멈춤현상이 발생하는데 왜그런걸까요..ㅠ

  11. 너구리 2019.07.17 20:24

    안녕하세요

    일정을 입력할때 직접 시간같은것도 설정해서 입력 가능한지 여쭤봅니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.17 22:04 신고

      SimpleDateFormat 사용법에 대해 공부하여 다음 부분에서 datetime 에 원하는 날짜와 시간을 입력하면 가능합니다.

      simpledateformat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss+09:00", Locale.KOREA);
      String datetime = simpledateformat.format(calander.getTime());

  12. 뚱이 2019.07.17 20:29

    안녕하세요
    혹시 구글 캘린더 일정을 삭제하는거 방법을 알고싶은데 알려주실수 있나요.

  13. 안녕하세요 2019.07.18 13:23

    안녕하세요

    이벤트를 입력할때 해당 날짜에 지정하여 일정을 입력할수 있는지 알고 싶습니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.18 14:58 신고

      가능합니다. 날짜 지정하는 UI만 추가하면됩니다. 다음 부분이 날짜를 지정하는 부분입니다.

      SimpleDateFormat에 대해 알아보세요

      dateformat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss+09:00", Locale.KOREA);
      String datetime = simpledateformat.format(calander.getTime());

  14. 헬로월드 2019.07.23 14:31

    안녕하세요

    혹시 add Event에서
    DateTime start = event.getStart().getDateTime();

    이 메서드에서 시간이 2019-07-23T13:30:00.000+09:00 이런식으로 나오는 것을 ,

    > 13:00 요런식으로 단순하게 표현할 수 있는 방법이 있을까요??

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.23 14:46 신고

      SimpleDateFormat를 사용해서 바꿀 수 있습니다. 아래 처럼 포맷을 지정해주는 겁니다.

      simpledateformat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss+09:00", Locale.KOREA);
      String datetime = simpledateformat.format(calander.getTime());


      다음 링크를 검토해보세요.
      https://hyeonstorage.tistory.com/232

    • 헬로월드 2019.07.23 14:47

      감사합니다.

    • 헬로월드 2019.07.23 14:48

      아..
      제가 잘못적었는데 수정을 어떻게 해야 될지 모르겠네요...

      addEvent가 아니라,
      getEvent 입니다..=-=;

  15. 헬로월드 2019.07.23 14:37

    참...

    현재 해보니 해당 캘린더의 모든 일정을 가지고 오더라구요~~

    getEvent 버튼을 클릭했을 시, 달력 혹은 datePicker가 뜨고 확인을 누르면 해당 일정을 가져오는 방법이 있을까요??

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.23 14:51 신고

      찾아보니 아래 처럼 지정하면 된다고 합니다.

      .setTimeMin(시작날짜지정)
      .setTimeMax(끝날짜지정)

      포스트 코드에서 setTimeMin가 주석되어 있는 부분에 적어주면 될듯합니다.

  16. 헬로월드 2019.07.23 15:10

    안녕하세요

    혹시 getEvent에서
    DateTime start = event.getStart().getDateTime();

    이 메서드에서 시간이 2019-07-23T13:30:00.000+09:00 이런식으로 나오는 것을 ,

    > 13:00 요런식으로 단순하게 표현할 수 있는 방법이 있을까요??

    ================================

    제가 addEvent라고 적었는데 수정을 못하겠네요.


    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.23 16:42 신고

      아래 글을 참고하면 됩니다.

      https://hyeonstorage.tistory.com/232


      예를 들어

      날짜 시간 보여줄 포맷을 정하고
      simpledateformat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss+09:00", Locale.KOREA);

      현재 시간을 위에서 정의한 포맷으로 변환하여 보여줍니다.
      String datetime = simpledateformat.format(calander.getTime());

  17. leekun3 2019.08.08 16:59

    안녕하세요
    도움 많이 받고 있습니다! 작성자님이 게시해주신 버그 수정 내용을 적용해도

    Attempt to invoke virtual method 'java.util.List com.google.api.services.calendar.model.CalendarList.getItmes()' on a null object reference

    에러가 발생하네요.
    다시 한번만 확인해주실 수 있나요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.08 17:40 신고

      확인해보겠습니다..

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.08 21:08 신고

      코드 문제는 아니고 OAuth 2.0 등록 방법이 바뀌어서 생긴 문제입니다.

      업데이트 해놓았으니 포스트를 보고 "1. Google Calendar API 사용 설정"을 다시 진행해보세요.

    • leekun3 2019.08.13 10:37

      감사합니다 ㅎㅎ

  18. sonar2 2019.08.30 15:32

    따라해 보니 잘 되네요... 감사합니다.

  19. lunar 2019.09.30 16:18

    안녕하세요 실행하려고 하면 계속 다음과 같이 오류가 뜨는데 혹시 도움을 주실 수 있나요?
    Duplicate class android.support.v4.app.INotificationSideChannel found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)
    Duplicate class android.support.v4.app.INotificationSideChannel$Stub found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)
    Duplicate class android.support.v4.app.INotificationSideChannel$Stub$Proxy found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)
    Duplicate class android.support.v4.os.IResultReceiver found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)
    Duplicate class android.support.v4.os.IResultReceiver$Stub found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)
    Duplicate class android.support.v4.os.IResultReceiver$Stub$Proxy found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)
    Duplicate class android.support.v4.os.ResultReceiver found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)
    Duplicate class android.support.v4.os.ResultReceiver$1 found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)
    Duplicate class android.support.v4.os.ResultReceiver$MyResultReceiver found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)
    Duplicate class android.support.v4.os.ResultReceiver$MyRunnable found in modules classes.jar (androidx.core:core:1.1.0) and classes.jar (com.android.support:support-compat:27.1.0)

    Go to the documentation to learn how to Fix dependency resolution errors.

    Go to the documentation to learn how to Fix dependency resolution errors.
    at com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable.run(CheckDuplicateClassesDelegate.kt:132)
    at com.android.ide.common.workers.ExecutorServiceAdapter$submit$submission$1.run(ExecutorServiceAdapter.kt:40)
    at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinTask.externalInterruptibleAwaitDone(ForkJoinTask.java:361)
    at java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:1001)
    ... 81 more

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.09.30 17:06 신고

      포스트에선 androidx를 사용하고 있는데 android.support로 바꾸어 진행하신거 같군요.. 맞나요?

      그렇다면 androidx로 진행해야 해결될듯합니다.

  20. eomsso 2019.11.20 11:50

    안녕하세요~
    작성자님이 게시해주신 버그 수정 내용을 적용하고, 포스트를 보고 "1. Google Calendar API 사용 설정"을 다시 진행해보았는데도

    Attempt to invoke virtual method 'java.util.List com.google.api.services.calendar.model.CalendarList.getItmes()' on a null object reference
    오류가 뜹니다ㅠㅠ 혹시 한번만 더 확인해 주실 수 있으실까요??

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.11.21 09:29 신고

      "1. Google Calendar API 사용 설정"에서 발생한 문제일 수 있습니다. 한번 다시 진행해보세요.. 코드 확인은 좀 시간이 걸릴 듯합니다....

    • BlogIcon 312 2020.09.07 01:31

      디코딩 버전과 릴리즈 버전 간에 인증코드가 다릅니다. 릴리즈 app 용 sha1코드를 받아 인증하시면 될 수도 있습니다.

+ Recent posts