Google API 第4回

投稿者: | 2015年2月4日

今回はCalendar APIのイベント(予定)の取得・挿入・更新・削除を行う方法を紹介します。イベントの操作はカレンダーの操作と同じような処理をするので理解しやすいと思います。
イベントの操作はGoogle API 第3回で使用したサンプルを変更して行います。
第3回のサンプルでは、マイカレンダーの取得や新たなカレンダーの追加、既存のカレンダーの更新、削除が行えます。
今回は第3回のサンプルを変更してカレンダーの予定を取得、新たな予定の追加、既存の予定の更新、削除をできるようにしていきます。

・イベントの処理をするファイルと説明
・ファイルのソースを変更する

開発環境はGoogle API 第3回と同様です。
※開発環境やサンプルについては参考サイトのGoogle API 第3回を参考にしてください。

イベントの処理をするファイルと説明
以下のファイルでイベントの取得・挿入・更新・削除・バッチ処理を行います。
○AsyncLoadEvents.java・・・取得
イベントのリストメソッドを用いて、カレンダーIDを指定するとイベントのリストを取得することができます。また、パラメータを指定することにより特定のイベントを取得することもできます。
○AsyncInsertEvent.java・・・挿入
イベントの挿入メソッドを用いて、カレンダーIDとイベントのオブジェクトを指定すると挿入ができます。
※イベントのオブジェクトには開始時間と終了時間の設定が必須です。
※今回はイベントの開始時間と終了時間をCalendarSampleActivity.javaのプログラム中に記述しています。
○AsyncUpdateEvent.java・・・更新
イベントの更新メソッドを用いて、カレンダーIDとイベントIDとイベントのオブジェクトを指定すると更新ができます。
○AsyncDeleteEvent.java・・・削除
イベントの削除メソッドを用いて、カレンダーIDとイベントIDを指定すると削除ができます。
○AsyncBatchInsertEvents.java・・・バッチ処理
バッチ処理を用いてイベントの複数挿入を行うことができます。
この処理を用いると複数更新するイベントがあった場合、まとめて更新を行うことができます。

ファイルのソースを変更する
まず、イベントの操作だということを分かりやすくするために
ファイル名の「Calendar」部分を「Event」に変更します。
例えば「AsyncLoadCalendars.java」の場合は「AsyncLoadEvents.java」に変更します。ソース内のファイル名も同様に変更します。
次に、ソースの変更ですが変更箇所が多いので付録に抜粋しています。参考にしてください。

ソースを変更しアプリケーションを実行すると下記のような画面にイベントが表示されます。

※その他の画面についてはGoogle API 第3回と同じなので省略します。
見た目は第三回のサンプルと変わりませんが
カレンダーの予定を簡単に操作できるので是非試してみてください。

Calendar APIについては今回で最後になります。
Calendar APIの操作がなんとなく理解できてもらえれば幸いです。
次回はGoogle Maps APIについて紹介したいと思います。

<参考サイト>
Google API 第3回

<付録>
変更部分のソースを抜粋しています。
※ファイル名は「Calendar」部分を「Event」に変更しています。
・AsyncLoadEvents.java
コンストラクタ、doInBackgroundメソッド内のソースを編集します。
下記のように編集することで、イベントのリストが取得できるようになります。

  AsyncLoadEvents(CalendarSampleActivity calendarSample) {
super(calendarSample);
}
@Override
protected void doInBackground() throws IOException {
Events feed = client.events().list(calendarId).execute();
model.reset(feed.getItems());
}

・AsyncInsertEvent.java
変数宣言、コンストラクタ、doInBackgroundメソッド内のソースを編集します。
下記のように編集することで、イベントの挿入ができるようになります。

  private final Event entry;
AsyncInsertEvent(CalendarSampleActivity calendarSample, Event entry) {
super(calendarSample);
this.entry = entry;
}
@Override
protected void doInBackground() throws IOException {
Event event = client.events().insert(calendarId, entry).execute();
model.add(event);
}

・AsyncUpdateEvent.java
変数宣言、コンストラクタ、doInBackgroundメソッド内のソースを編集します。
下記のように編集することで、イベントの更新ができるようになります。

  private final Event entry;
AsyncUpdateEvent(CalendarSampleActivity calendarSample, String eventId, Event entry) {
super(calendarSample);
this.eventId = eventId;
this.entry = entry;
}
@Override
protected void doInBackground() throws IOException {
try {
Event updatedEvent = client.events().update(calendarId, eventId, entry).execute();
model.add(updatedEvent);
} catch (GoogleJsonResponseException e) {
// 404 Not Found would happen if user tries to delete an already deleted calendar
if (e.getStatusCode() != 404) {
throw e;
}
model.remove(eventId);
}
}

・AsyncDeleteEvent.java
変数宣言、コンストラクタ、doInBackgroundメソッド内のソースを編集します。
下記のように編集することで、イベントの削除ができるようになります。

  private final String eventId;
AsyncDeleteEvent(CalendarSampleActivity calendarSample, EventInfo eventInfo) {
super(calendarSample);
eventId = eventInfo.id;
}
@Override
protected void doInBackground() throws IOException {
try {
client.events().delete(calendarId, eventId).execute();
} catch (GoogleJsonResponseException e) {
// 404 Not Found would happen if user tries to delete an already deleted calendar
if (e.getStatusCode() != 404) {
throw e;
}
model.remove(eventId);
}

・AsyncBatchInsertEvents.java
変数宣言、コンストラクタ、doInBackgroundメソッド内のソースを編集します。
下記のように編集することで、バッチ処理でイベントの挿入ができるようになります。

  private final List<Event> events;
AsyncBatchInsertEvents(CalendarSampleActivity calendarSample, List<Event> events) {
super(calendarSample);
this.events = events;
}
@Override
protected void doInBackground() throws IOException {
BatchRequest batch = client.batch();
for (Event event : events) {
client.events().insert(calendarId, event).setFields(EventInfo.FIELDS)
.queue(batch,  new JsonBatchCallback<Event>() {
public void onSuccess(Event event, HttpHeaders headers) {
model.add(event);
}
@Override
public void onFailure(GoogleJsonError err, HttpHeaders headers) throws IOException {
Utils.logAndShowError(activity, CalendarSampleActivity.TAG, err.getMessage());
}
});
}
batch.execute();
}

・EventAsyncTask.java
変数宣言、コンストラクタのソースを編集します。
下記のように編集することで、イベントの操作で必要な引数が簡単に参照できるようになります。

  final CalendarSampleActivity activity;
final EventModel model;
final com.google.api.services.calendar.Calendar client;
final String calendarId;
private final View progressBar;
EventAsyncTask(CalendarSampleActivity activity) {
this.activity = activity;
model = activity.model;
client = activity.client;
calendarId = activity.calendarId;
progressBar = activity.findViewById(R.id.title_refresh_progress);
}

・EventInfo.java
下記のように編集することで、イベントのフィールドを扱いやすくします。

  static final String FIELDS = "id,summary";
static final String FEED_FIELDS = "items(" + FIELDS + ")";
String id;
String summary;
EventInfo(String id, String summary) {
this.id = id;
this.summary = summary;
}
EventInfo(Event event) {
update(event);
}
@Override
public String toString() {
return Objects.toStringHelper(EventInfo.class).add("id", id).add("summary", summary)
.toString();
}
public int compareTo(EventInfo other) {
return summary.compareTo(other.summary);
}
@Override
public EventInfo clone() {
try {
return (EventInfo) super.clone();
} catch (CloneNotSupportedException exception) {
// should not happen
throw new RuntimeException(exception);
}
}
void update(Event event) {
id = event.getId();
summary = event.getSummary();
}

・EventModel.java
下記のように編集することで、画面にイベントを表示させることができるようになります。

  private final Map<String, EventInfo> events = new HashMap<String, EventInfo>();
int size() {
synchronized (events) {
return events.size();
}
}
void remove(String id) {
synchronized (events) {
events.remove(id);
}
}
EventInfo get(String id) {
synchronized (events) {
return events.get(id);
}
}
void add(Event eventToAdd) {
synchronized (events) {
EventInfo found = get(eventToAdd.getId());
if (found == null) {
events.put(eventToAdd.getId(), new EventInfo(eventToAdd));
} else {
found.update(eventToAdd);
}
}
}
void reset(List<Event> eventsToAdd) {
synchronized (events) {
events.clear();
for (Event eventToAdd : eventsToAdd) {
add(eventToAdd);
}
}
}
public EventInfo[] toSortedArray() {
synchronized (events) {
List<EventInfo> result = new ArrayList<EventInfo>();
for (EventInfo event : events.values()) {
result.add(event.clone());
}
Collections.sort(result);
return result.toArray(new EventInfo[0]);
}
}

・CalendarSampleActivity.java
下記のように編集することで、イベントの操作ができるようになります。
大きな変更部分はカレンダーIDの保持やイベントオブジェクトの生成です。

  //CalendarModel model = new CalendarModel();
EventModel model = new EventModel();
//ArrayAdapter<CalendarInfo> adapter;
ArrayAdapter<EventInfo> adapter;
//追加
String calendarId = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// enable logging
Logger.getLogger("com.google.api.client").setLevel(LOGGING_LEVEL);
// view and menu
setContentView(R.layout.calendarlist);
listView = (ListView) findViewById(R.id.list);
registerForContextMenu(listView);
// Google Accounts
credential = GoogleAccountCredential.usingOAuth2(this, Arrays.asList(CalendarScopes.CALENDAR));
SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
calendarId = settings.getString(PREF_ACCOUNT_NAME, null);
// Calendar client
client = new com.google.api.services.calendar.Calendar.Builder(transport, jsonFactory, credential).setApplicationName("Google-CalendarAndroidSample/1.0").build();
}
void refreshView() {
adapter = new ArrayAdapter<EventInfo>(
this, android.R.layout.simple_list_item_1, model.toSortedArray()) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// by default it uses toString; override to use summary instead
TextView view = (TextView) super.getView(position, convertView, parent);
EventInfo calendarInfo = getItem(position);
view.setText(calendarInfo.summary);
return view;
}
};
listView.setAdapter(adapter);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_GOOGLE_PLAY_SERVICES:
if (resultCode == Activity.RESULT_OK) {
haveGooglePlayServices();
} else {
checkGooglePlayServicesAvailable();
}
break;
case REQUEST_AUTHORIZATION:
if (resultCode == Activity.RESULT_OK) {
AsyncLoadEvents.run(this);
} else {
chooseAccount();
}
break;
case REQUEST_ACCOUNT_PICKER:
if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) {
String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
credential.setSelectedAccountName(accountName);
SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString(PREF_ACCOUNT_NAME, accountName);
editor.commit();
AsyncLoadEvents.run(this);
}
}
break;
case ADD_OR_EDIT_CALENDAR_REQUEST:
if (resultCode == Activity.RESULT_OK) {
String id = data.getStringExtra("id");
Event event = new Event();
event.setId(id);
event.setSummary(data.getStringExtra("summary"));
Date startDate = new Date();
Date endDate = new Date(startDate.getTime() + 3600000);
DateTime start = new DateTime(startDate, TimeZone.getTimeZone("UTC"));
event.setStart(new EventDateTime().setDateTime(start));
DateTime end = new DateTime(endDate, TimeZone.getTimeZone("UTC"));
event.setEnd(new EventDateTime().setDateTime(end));
if (id == null) {
new AsyncInsertEvent(this, event).execute();
} else {
//event.setId(id);
new AsyncUpdateEvent(this, id, event).execute();
}
}
break;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
AsyncLoadEvents.run(this);
break;
case R.id.menu_accounts:
chooseAccount();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
int eventIndex = (int) info.id;
if (eventIndex < adapter.getCount()) {
final EventInfo eventInfo = adapter.getItem(eventIndex);
switch (item.getItemId()) {
case CONTEXT_EDIT:
startAddOrEditEventActivity(eventInfo);
return true;
case CONTEXT_DELETE:
new AlertDialog.Builder(this).setTitle(R.string.delete_title)
.setMessage(eventInfo.summary)
.setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
new AsyncDeleteEvent(CalendarSampleActivity.this, eventInfo).execute();
}
})
.setNegativeButton(R.string.no, null)
.create()
.show();
return true;
case CONTEXT_BATCH_ADD:
List<Event> events = new ArrayList<Event>();
for (int i = 0; i < 3; i++) {
Event event = new Event();
event.setSummary(eventInfo.summary + " [" + (i + 1) + "]");
Date startDate = new Date();
Date endDate = new Date(startDate.getTime() + 3600000);
DateTime start = new DateTime(startDate, TimeZone.getTimeZone("UTC"));
event.setStart(new EventDateTime().setDateTime(start));
DateTime end = new DateTime(endDate, TimeZone.getTimeZone("UTC"));
event.setEnd(new EventDateTime().setDateTime(end));
events.add(event);
}
new AsyncBatchInsertEvents(this, events).execute();
return true;
}
}
return super.onContextItemSelected(item);
}
public void onAddClick(View view) {
startAddOrEditEventActivity(null);
}
private void haveGooglePlayServices() {
// check if there is already an account selected
if (credential.getSelectedAccountName() == null) {
// ask user to choose account
chooseAccount();
} else {
// load calendars
AsyncLoadEvents.run(this);
}
}
private void startAddOrEditEventActivity(EventInfo eventInfo) {
Intent intent = new Intent(this, AddOrEditEventActivity.class);
if (eventInfo != null) {
intent.putExtra("id", eventInfo.id);
intent.putExtra("summary", eventInfo.summary);
}
startActivityForResult(intent, ADD_OR_EDIT_CALENDAR_REQUEST);
}