2016년 12월 22일 목요일

Google Calendar API / App Inventor 2 Tutorial


How to view and INSERT calendar events using App Inventor 2 (with no web service scripts).


What's  new for AI2?


- The Parse procedure has gone. I found an easier way to extract data from the json response - the lookup in pairs block. This block is great as it does most of the work for you. You can see where it is being used in the screenshots.


➠ I have provided the .aia file here for you. In order for it to work, you must first get your own clientID by signing up to google apis (as per the instructions below), and enter it into the strClientID variable in the blocks editor.


➠ I have only used global variables in this example, in AI2 you now have the option of using local variables, which can make the app slightly more efficient. For an exercise, see if you can convert some of the globals to locals.



1. SETUP

First you need to register your app to use google api here https://console.developers.google.com/project


At the top select ‘CREATE PROJECT’
 0.png

Then on the top left, select APIs & Auth, then select Calendar API and turn ON.
 1.png


Then go to Credentials and click on ‘CREATE NEW CLIENT ID’.
 2.png


In the pop-up box, select ‘Installed Application’. For Installed Application Type, select ‘Android’.

It will then ask you for your package name* and SHA1 fingerprint**. Enter these into the boxes.

* Your package name is of the form appinventor.ai_<yourusername>.<yourappname>
(without the <>).
 3.png

**How to get SHA1 fingerprint (on PC):
Open Command Prompt on your PC. Navigate to your Java bin directory and type:
keytool -list -keystore c:\path_to_your_key_here.keystore
It will prompt to enter your keystore password, then will display your SHA1 fingerprint. Copy and paste it into the above screen. Then click Create Client ID.
 4.png
You will then get a summary of your Client ID etc. We need to put these into your app.
 5.png

2. GETTING AUTHORISATION
a). Get the user to login using Oauth

These sections take place in AppInventor. Here we will use a Webviewer component and some variables.

Enter your Client ID and Redirect URI (the urn:ietf:wg:oauth:2.0:oob one) into two variables (see screenshot below). The variable strResponseType should be initialized as the string “code”.

Construct the OAuth URL as outlined here https://developers.google.com/accounts/docs/OAuth2Login.

The scopes we need to use are:

read/write access to Calendars
read-only access to Calendars

We also need to make sure to include &access_type=offline.

Then start the Webviewer at the constructed URL:
 6.png

In the Webviewer, the user is then prompted to enter their google account info and then to ‘Allow access’.
 7.png

We will use Taifun’s trick of using a clock to monitor the WebViewer PageTitle. This returns an AuthCode. So we want to put this in a variable.
 8.png

We can now exchange this AuthCode for an Access Token and a Refresh Token.

b). Get an Access Token and Refresh Token -
We then need to do a Web POST using the Web component, to get an access token and a refresh token. We point the Web url to https://accounts.google.com/o/oauth2/token.

We then construct the headers, URL and POST data like so (note the AuthCode we got in the previous step is sent in the POST data):
 9.png

NOTE: As you can see above, the Request Header must contain
Content-Type: application/x-www-form-urlencoded.

The Post Data must contain the following key value pairs, which are put in lists:
Code: {your AuthCode}
client_id: {your ClientID}
redirect_uri: {your RedirectURI}
grant_type: authorization_code

You can see in the above screenshot we are using the AuthCode from step 2a) in this call.

Now we use the Web1.GotText block to let us know if the response was successful, and if so, extract from the response content the access token and refresh token, using some lookup in pairs blocks.

We then put these values into two variables strAccessToken and strRefreshToken:
 9.png


NOW WE HAVE ACCESS AND WE CAN DO LOTS OF CALENDAR THINGS! Here are three examples from the app:

3. GETTING A LIST OF CALENDARS

A user can have more than one calendar, so we can get a list of all of their calendars so they can choose which one they want to work on. We do this by using the Web component again and doing a Web1.Get to the following URL:

(Note we are using the access token we received in the previous step). Here’s the blocks to do that:
 10.png

We then use the Web1.GotText block for the response. When the response is successful (if response code = 200), we can get all their calendar IDs and put them in a list:
 11.png
The getCalendarIds procedure we have called above is shown here: Note we are using lookup in pairs again to extract the data.
 12.png

We can then stick these values into some buttons and offer the user an option to choose which calendar they want to work on:
 12.png

The example .apk and .aia contains 2 further examples of working with the calendar API - displaying calendar entries, and inserting a calendar entry.

4. DISPLAYING CALENDAR ENTRIES

Now that the user has selected a calendar, we can retrieve a list of entries from their calendar and display it in the app.

We do this by using Web1.Get again. According to the API docu, the URL for getting calendar entries is:

So we simply construct this URL (including the access token) in App Inventor like so (note we are using the selected CalendarID from the previous step). For the example we are also making MaxResults=5 and SingleEvents=true.
 13.png
Then we use Web1.GotText again to see if we get a successful response. If successful, we can again start a procedure which uses lookup in pairs to extract the results and display the items on the screen:
 13.png
Here is the procedure getEventData that grabs the data and displays it on the screen:
 14.png

And the result:
 15.png


5. INSERTING A CALENDAR ENTRY

Now we come to inserting a calendar entry. According to the API docu, we need to do a Web HTTP POST to the following URL.

We have done a POST before so we can do the same again easily enough.

The example app uses Textboxes so the user can enter the details of the event. We want the following things from the user:

Date
Start Time
End Time
Event Title
Event Description
Location (where is it?)
Do they want a reminder?
 16.png

When the user submits this form, first we need to construct the start dateTime and end dateTime in RFC3339 format, which is what google calendar accepts. We can do this by using a text join block, getting the items from the text boxes and interspersing them with the correct text pieces (note we are also setting addReminder to true or false depending on the checkbox input):
 16.png













fanfare.mp3

--

Nicely done, Steve!
Thanks for sharing this/

-- 
Great stuff, thanks Marcus!

--
This is great! I am planning to make a project that helps you manage your homework assignments and test for school. Do you know how I could get it to remind me whenever there is an upcoming event?

--
Yes, have a look at inserting the calendar event, you have the option there of setting a reminder. If it is set, a reminder will pop up on your phone. You can also look at sending reminder type and reminder time (e.g 15 minutes before, 1 hour before) in your call.

--
I download and installed the apk file, the first step OAuth, I have no problem, but when I click the 2. get Token, it return Error getting data - try again. may I know did I miss some steps that I might have to do before I can get it run?

--
Hmm not sure what it could be, it worked for me. You could try load the aia file and run the app that way to find out where the error is (maybe see what you get back in the response content for getToken).

--
Thanks again for all your help! I was wondering was the web1.gottext block cropped because we don't need to fill that part out or was there something there?

--
It was cropped just because some blocks were not relevant to that particular step. They appear in the previous steps.

--
I cannot replicate the invalid_grant error.

Some things to try:

Add access_type=offline in your request

Double check the redirect_uri is the same as in your google api console

Make sure the time on your clock is the correct time

The refresh token limit has been exceeded

-- 
Also of course check your AuthCode is a valid code and it is returning AuthCode properly in the previous step (since we use that in the call).

--
Thanks so much for posting the .aia file. No one else has indicated that they have had this problem. But when I download the .aia to my computer, then import it into AI2, the blocks don't load properly. For example, for Screen1 in Blocks mode, I get the message, "The blocks area did not load properly. Changes to the blocks for screen 4618364592848896_Screen1 will not be saved.". I get similar messages for both of the other screens. Any insights?
-- 
Hi Susan,

Hmm not sure what that could be. I just tested it and it is loading up fine for me.

Have a look over at the MIT App Inventor forums, there are some posts about it..

Otherwise create a new post there and maybe someone can help you.

--
I posted to the App Inventor forum and got the answer. For others: I imported the .aia using Chrome (I was using Firefox), closed all of the comment bubbles, and switched back over to Firefox. No error message and all of the code was there! Thanks so much! 
-- 


Glad you got it sorted out.
I used to use Firefox with App Inventor but as my projects grew in size started to have memory issues.
I moved to Chrome which seems to handle memory better.

--
I'm getting error 400 when start using the GoogleCalendarAPI demo apk

How can avoid this message

--
I am not familiar with phantomfoot's calendar app, but a search for the error 400 would indicate that the problem may be in the data you are entering that gets attached to the url for the calendar api.
Check if you are entering the correct data. Does he provide an example to use for testing?

--
This is an awesome tutorial, thanks! Can this be used just to identify the user and gets its data?

--
I think you would need the user's password as well as their email address.
 hAPPyINVENTORing.gif
--
This is not working I'm receiving error invalid_grant and I already check what you suggest above, any other hint?
--
Hi Gabriele-
I host the tutorial here but unfortunately don't know how the demo app works.

--
As stated before I cannot replicate the error, so I am not sure why you are getting it. Are you using the apk or aia? And where exactly is the error occurring?

--

I'm using the aia, but I'll try the apk too. The error occurs right after I press get token button
--
Ok I tried the apk and getting the same error

--

Ok I finally get to adapt your tutorial for authenticate an user by oauth, just like the taifun's tutorial. I'll post the aia some of this days if it can be useful for somebody

--

As I tell my friends - it's better to have it and not need it than to need it and not have it!
-- 
Hi Gabriele, I too am facing the same error that you did. Could you please share your updates to the program? Or even better, upload the aia file.

--
Hi JVG, the solution is here:
https://groups.google.com/forum/#!topic/mitappinventortest/uP9cqB3Q8EY

Basically, open with chrome, close comment bubbles, then open with Firefox.

--

Thank you phantomfoot for your quick response and the well written tutorial. 

I did as you instructed me to, but I'm still getting the error. After obtaining the AuthCode when I click on the 'getToken' button, the label displays the message 'Error getting data-try again'. Upon further investigation it seems like a code 400 error.

--
Hmm 400 means bad request - something is not right in the request url. Not sure but you could check the redirect_uri is the same as the one in your client registration. Also check response_type is correct, or you could try to remove response_type altogether.

--
Hi phantomfoot, I sorted out the issue by adding the 'Client Secret' in the request url.

However I have run into another roadblock and needed your assistance. In my android app, I am trying to insert a row into a fusiontable. I used your code to successfully obtain the Access Token and Refresh Token. In order to insert a row, I tried using Puravida's snippet (Link  see the Example: Insert section) however I get a 403 Error (Forbidden Access).What do you reckon might be causing this error?

--
Hi everyone. As part of a high school project, I ended up here. I am looking to create a calendar app for students at my high school to download and view the school's events. I downloaded the .aia file and got it opened up in AI2. However, I am new to this and still have a few questions:

1. Screen1 has a ton of blocks at the top it, most with a text box but no text. Do I need to fill these in?
2. A note on the second set of blocks says something like, "After the user allows access, get the AuthCode and put it in a variable." What does this entail? Exactly which variable do I put the AuthCode into?
3. The next note says, "Get the calendar IDs and put them in a list". What does this entail?
4. Next is, "Loop over the content and extract each calendar ID, and add them to a list." Again, totally clueless as to what this means.
5. Is this even a good way to go about having hundreds of people view the same google calendar? Will they all have to log in once they download the app?

Sorry if I sound completely clueless. I am. But I guess this is just step one at getting good at making apps.
--
Those are all just comments, telling you what each set of blocks does. You don't need to do anything for any of those. Just get your API clientid info and enter it in the variables as instructed.
5. You would have to share the calendar with everyone you want to access. Other than that it should be ok. Run the app and see. But you may need some experience first in using ai and APIs, this could be considered fairly advanced for beginners.
--
To my case, it was the message that google returns back, its not in english, because i were in non western country, so u have to modify that message to match it

--

I am new in AI2.  I cannot get the SHA1 fingerprint by keytool.  Is it necessary to generate the keystore before hand?  If so, how to create?  I just get no keystore file after typing the keytool cmd.  Thanks a lot.

*How to get SHA1 fingerprint:
You will need your keystore file. Open Command Prompt on your PC. Navigate to your Java bin directory and type:


keytool -list -keystore c:\path_to_your_key_here.keystore


It will prompt you to enter your keystore password, then will display your SHA1 fingerprint.

--
 Is it necessary to generate the keystore before hand? 

Yes, you need to download the keystore file first from AI2, Go to Projects -> Export keystore.
--
This is awesome.

For anyone trying to go through the tutorial, like I did, I would suggest just alter the AIA file to suit.

I've been working on it for days and I only just realised there was a file... my bad.

--
I'm so happy to get this working I could do a happy dance

--
Very very good job ;-)
Just a question...while using the demo app I clicked the second button " get token"....then I get an error called
Error getting data try again. ..
AT the first step I am not able to input my Google name/password because it seems that the Web viewer is closed after 2 seconds....is it normal?  The first step "start oAuth" works...I get the message "successful..."

--

Hmm, I cannot replicate this with the app. Possibly a device-specific error?
If you wish to find out exactly the error - at Web1.GotText you can see what the error might be by setting the lblResult to the responseCode and the responseContent.

The web viewer closing after 2 seconds should not happen, this will be in the clock1.timer block at the point where it says: if WebViewer1.CurrentPageTitle does not contain 'Google Accounts' and if WebViewer1.CurrentPageTitle does not equal 'Request for Permission'. 

You can adjust these if necessary to see what is causing the issue.

This tutorial is fairly old now and there are probably better ways of doing oAuth with app inventor these days...

--
What a pitty. ..I am so glad that I found your tutorial because all other possibilitys like using an activity starter is nasty...u cannot put in a special date just a title and u don't get any results....
I tried ur apk at three different devices with different android versions....apk friend of mine told me that Google switched their system to ssl about one year ago...might that be a reason? Or has anybody of u create an up-to-date version of using the Google API?
I am trying to find out which error I got back...

--

I think I found the mistake...the auch Code should be the window title without "success="...In my case the automatic "allow access" function does not work...my Code is simply "allow app to access..." Not the code after I pressed the button "allow"....So can anybody tell me why the app does not allow access at its own?
--
I found the mistake....very simple...I live in Germany. ...So the title of the Web viewer window was English  (access to permission?) ..i just changed it into the German title, now it works fine ;-)
--
Well done! I actually didn't think of that - so simple. Great news.
--
I am just working at an single screen version with different arrangements which are shown as visible and not visible...I got the problem, that sometimes the screens don't switch to the other...So I am just including your .aia in my little organiser. ..It is almost ready. Thanks again ;-)

--
I got another question. Everytime, when I start the app and try to connect to the google calendar google asks me for "Request for Permission" (see the picture) so that I first have to change the script, that the webviewer is visible until I clickt "access" 
So what is my mistake? Have I setted something wrong in my developer console with my appname, the fingerprint, client ID or something else? Or ist this a normal step in this google api aia?

--
Hi Thomas-
This is what I found re. your issue:

--
Hmm. ...the only difference between my version and the original from phantom foot is that I doesnt use the https://www.googleapis.com/auth/calendar.readonly. i use https://www.googleapis.com/auth/calendar....i thought that this is necessary to change events or stuff like this but I am gonna check it out....
If that doesn't work I really don't know what I am doing wrong with the oauth request scope stuff and the other things I don't understand ;-)

--

Hi there, if you look at the usual Google OAuth flow, in Java you would normally store the access token and refresh token for later use. You could also do this in the above example by storing the variables strAccessToken and strRefreshToken in the TinyDB.
Then, the next time the user starts the app, bring these variables back in from the TinyDB. You can then try to use these to immediately call the 'callAPI' function therefore bypassing the need to request permission every time.

If the token has expired (if you get an incorrect response from callAPI), then you can also account for this in your code, so the end product will be it will show the 'Allow access' only when it is absolutely necessary (only when the token has expired) .

--
I apologize for the very beginner question, but is a Google Developers Console account required for this?  We aren't sure where the username comes from.

--
I was just trying to figure out the same thing, is it my google username, my ai2 username? A new username I have to make somewhere?

--
In addition to the above question, I also am having trouble finding how to get the 
In fact, my searches tell me that SHA-1 is outmoded or something?

--
I'm told I shouldn't be asking the above questions in another thread, and I apologize for bumping this one. But Amy has been waiting almost a month and I'm at a dead standstill. 

I tried just making up a username, but I can't tell if it works because I can't get past the SHA1 Fingerprint. The lovely folks over at the Chromebook help forum told me to ask my chromebook questions somewhere else because I dared to mention AI2, so now I have to come back here. 

--
I host this forum but do don't know how the calendar app works.
phantomfoot was answering the support questions up until Nov 19, I see.
Wish I could be more help, but I am familiar only with projects that I have created here.

--
is a Google Developers Console account required for this?  We aren't sure where the username comes from. 
no, you only need a Google account to be able to login to the Google API Console here https://code.google.com/apis/console#access

--
 I can't tell if it works because I can't get past the SHA1 Fingerprint
just do it the easy way and use Other as application type instead of Android...
Later you can get an SHA1 fingerprint as described here https://developers.google.com/console/help/new/#installed_applications

--
Thanks for the responses, I will be pursuing them immediately. But Amy may still be wondering exactly what username is expected in the application package name ("appinventor.ai_<USERNAME>_<applicationname>").

SHA1 is still not going to be available to me I guess, since Chromebook's ChromeOS does not have a console, or a java directory. I tried looking at the Keystore file directly and found interesting numbers but none of them clearly a SHA1 40 digit 16bit number string.

Thanks again, I will post anything I find on the subject that works.

--
But Amy may still be wondering exactly what username is expected in the application package name ("appinventor.ai_<USERNAME>_<applicationname>").
well, I guess in case she has a question she will ask? I could not find that question somehow?

but in case you have the same question (?) the username is the username you are using for your Google login
btw. don't hesitate to do a search in the MIT App Inventor forum, that question has been answered several times...

--
Amy's question was just above my first question in this thread. Maybe I misinterpreted it?

-- 
I'm having trouble with this step. Pardon my skill in App inventor 2, I'm still relatively new to it. When I put in the command line shown in the picture you provided, its says "'program' is not a recognized as an internal or external command". Did I install Java wrong or is it something wrong with my PC. I'm using windows 8.1 64 bit.

--
Did you navigate to your Java bin directory first? The command for change directory is cd. The Java bin directory is usually in c:\program files\java

--
From my understanding how the directory works, I have to type the full version of the Java Runtime Environment. In the cmd image you posted, you only typed jre7 while in other tutorials I looked at they typed jre1.7.0_73 or some other jre version. Am I suppose to the full version name or just jre7?

--
If you are referring to this:

**How to get SHA1 fingerprint (on PC):
Open Command Prompt on your PC. Navigate to your Java bin directory and type:
keytool -list -keystore c:\path_to_your_key_here.keystore

I would think that you would enter the path which includes '...jre1.7.0_73...' (replacing jre1.7.0_73 with your java version, of course) plus the rest of the path to where the .keystore file is located.

--
Hi Kevin, yes you want to type in the full version name ie. the full directory path,
So you would type for example cd Program Files (x86)\Java\jre1.7.0_73\bin
It's whatever directory on your PC where Java is installed.
Then you type in the keytool command. --
 Nevermind people, I figured it out myself. Thanks for your reply though.

--
I ran into another problem. I gotten an error 400; "missing required parameter: redirect_uri". I assume this is something to do with strOAuthURL.However, You said to construct the urls at the link you provided. That didn't seem to help. (or so I think)

According to https://developers.google.com/accounts/docs/OAuth2Login it says to go to credentials to set the redirect uri but it doesn't show up. I assume this suppose to fix my problem unless I'm reading it wrong.

--
This probably has to do with strRedirectUri. Check your strRedirectUri variable has a value and is of the form 

urn:ietf:wg:oauth:2.0:oob

And check it is properly constructed in the strOathURL.

 img.png

Here is an example of what your url should end up looking like, from https://developers.google.com/identity/protocols/OAuth2InstalledApp:
https://accounts.google.com/o/oauth2/v2/auth? scope=whatever_scope_you_need& redirect_uri=urn:ietf:wg:oauth:2.0:oob& response_type=code& client_id=812741506391-h38jh0j4fv0ce1krdkiq0hfvt6n5amrf.apps.googleusercontent.com
see here for more information https://developers.google.com/identity/protocols/OAuth2InstalledApp under Choosing a redirect URI
It works fine for me.

--
how do you get the current events? i keep getting the same 5 events from when the calendar was created. I am really struggling to find or understand that information out there. 

--
Reading the docs here https://developers.google.com/google-apps/calendar/v3/reference/events/list#request you can also specify a timeMax and timeMin in the URL.

So you can put those in the url. timeMax should be today's date, timeMin is how far back you want to go, say 1 or two months. The dates have to be in RFC3339 format e.g. 2016-03-22T10:00:00Z

https://www.googleapis.com/calendar/v3/calendars/calendarID/events?maxResults=5&access_token=your_access_token&singleEvents=true&timeMax=2016-03-22T10:00:00Z&timeMin=2016-01-01T10:00:00Z

This will give you the 5 current entries between those two dates.

--
That is what I needed thank you so much. My other issue that seems to of come up is my 5 events are not in order by date. For example, I have the min set to today (using the clock) and the max set using the clock +5 days. It works great, however when I send that data to my 5 labels to show the start and end dates they are listing in random order. It would show label one date as 3/29/16, label 2 3/30/16, label 3 3/29/16, label 4 3/28/16 and label 5 3/28/16. All of the data is correct however I want it to read from today (or closest date) to furthest date. Any insight as to how i could achieve this?

--
Here's the relevant section of the doc referenced in an earlier post in this thread ...
stringThe order of the events returned in the result. Optional. The default is an unspecified, stable order. 

Acceptable values are:


 "startTime": Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEventsis True)


✫ "updated": Order by last modification time (ascending).


--
I apologize if i missed that in this thread, thank you for that. Does it need to go in a specific order of the URI or can it be added after the timeMAX/MIN?

--
I've never used the API, I just read the link myself.

But usually, in most web APIs, if the extra parms have names, they can go in any order.

--
Hmmm ive tried to get that orderby to work but no such luck. I looked at the api documents and still am lost. I have trouble translating the api code for a webpage to app inventor use.

--
I have problem with the token. Could you help me?
I followed all the steps.



--
 Were you able to solve your problems? We have the same idea as you, but we don't know how to link our specific calendar into our app.
 Any information helps.

--
======================================== 
**How to get SHA1 fingerprint (on PC):
Open Command Prompt on your PC. Navigate to your Java bin directory and type:
keytool -list -keystore c:\path_to_your_key_here.keystore
It will prompt to enter your keystore password, then will display your SHA1 fingerprint. Copy and paste it into the above screen.
========================================
I want to get sha1 fingerprint. but I don't know 'path_to_your_key_here.keystore'

Where is a  path_to_your_key_here.keystore?

I can't find this files....

please help me!

--
So what i failed to do the first time was download the keystore from app inventor. Then i needed to find the name of it and the file location/path. after i did that it worked.

--
Thanks for letting us know how you solved it :)

--
I've the same problem someone can help me

--

I can't seem to locate my java bin. I downloaded the keystore and ran the code from above. My command prompt says H:\> as opposed to the normal C. Thanks!

--
Has anyone made this work now that Google no longer allows webviewer to call oauth?

The change is documented here:

--
Unfortunately there is still no one who has found a solution. First I used a webview to authorize the use of the calendar and insert an event from my app, now Google no longer allows me to use the webview and my app reports: Error 403. That's an error.Error: disallowed_useragent.
I hope there is soon a solution. thank you

--
I haven't tried it but perhaps using tasker and the AutoCalendar plugin can get the data and send it to app-inventor.

--

what's AutoCalendar plugin? I don't know that.

I'm trying with activity starter. I can insert a new event with title, location and description but not the dates and time.

Is there anyone who knows how to provide the date and time in the new calendar event?
--

Did you fix it with the calendar?

I'm looking for a solution, but I did not succeed.

--

댓글 1개:

  1. Are you looking to make cash from your websites with popunder advertisments?
    In case you do, have you ever considered using Clickadu?

    답글삭제