Gmail
Blink Receipt Digital sdk allows for full Gmail Integration.
Gmail Client¶
The GmailClient
is the corner stone of the gmail sdk integration. It is the access point for reading and parsing emails from clients. It leverages Google’s task framework to allow for seamless and clear multi-threading functionality.
To instantiate the GmailClient
you must provide the constructor 3 non-null and non-zero arguments.
-
Context:
Context
. When using the client within an AndroidActivity
you can pass inthis
for the argument value. If using the client within an AndroidFragment
you can pass inrequireActivity()
. -
Thread Count:
int
which determines the number of threads to use for processinge-receipt
emails. The internal default value we use is4
. -
Client Id:
String
which comes from Google’s api services. Please refer here for more information on how to create an Android client id.
Code sample for Gmail Client Instantiation:¶
// Activity Example
class GmailActivity: Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
val gmailClient = GmailClient(this, 4)
}
}
// Fragment Example
class GmailFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val gmailClient = GmailClient(requireActivity(), 4)
}
}
Logging In To Gmail¶
Users may log in to gmail via the client’s login()
function. There is an overloaded login function which takes in an Android Activity
, login(Activity activity)
. Passing the Activity
allows for our components to be lifecycle aware and not leak memory. This parameter is optional though and not required for any explicit extra functionality.
The login call returns a Google Task
of type GoogleSignInAccount
. The GoogleSignInAccount
is an object which contains the basic account information of the signed in Google user. The reference for GoogleSignInAccount can be found here.
login()
can be called from any thread, because the return type is a Task<GoogleSignInAccount>
. If you read the previous section of this document on the Task framework you should have some familiarity with how this process works.
Let’s step through the different login scenario results that can occur and how to provide proper handling of each scenario.
Successful Login Scenario¶
A successful login attemt will return a valid GoogleSignInAccount
. This sign in account can be captured via an OnSuccessListener
call.
Here is an example of some happy case scenarios when calling the login() function.
fun loginUser() {
val task: Task<GoogleSignInAccount> = gmailClient.login()
task.addOnSuccessListener {
}
}
Unsuccessful Login Scenario¶
A login attempt can fail for a number of reasons. Google has integrated its sign in flow into the Android system. For safety reasons, calling login does not always automatically sign the default user in to your application. It is possible that Google may require extra authentication when you attempt to signIn. The Gmail Integration of the sdk provides an easy to use wrapper around Google’s authentication handling. When calling login it is possible for exceptions to be thrown. We try to make exception handling as easy as possible for you. Therefore, we have created our own easy to use exceptions that will allow your app proper recourse in the event of a failure.
GmailAuthException
One of the most important exceptions to look out for is the GmailAuthException
. This exception occurs in the event of any silent authentication exceptions. This exception is extremely important, because it could potentially contain a recourse for a user to take upon an unsuccessful sign in. The exception potentially contains an Android Intent
this is an intent that has been provided by Google and meant to be launched by the app developer to obtain an explicit approval from the app user. The intent must be triggered with a startActivityForResults()
call. This call is a method within Android components (Activity, Fragment). It takes in an Intent
as well as an int
which denotes the identifying requestCode
. Starting this activity for result will display an overlaying window that will display the user’s registered accounts on the device.
Once a user has selected their desired account the overlaying screen will automatically dismiss and the onActivityResult(int requestCode, int resultCode, Intent data)
callback overridden within your Android component (Activity or Fragment) will be invoked. There is no need to handle the result and data yourself, the gmail client provides an easy to use function to handle this. Simply call gmailClient.onAccountAuthorizationActivityResult()
, passing in the requestCode
, resultCode
, and data
respectively.
The onAccountAuthorizationActivityResult
function also returns a task of type GoogleSignInAccount
. If the sign is successful then the Intent data passed in usually contains the desired GoogleSignInAccount
which the client parses and returns to you in the form of a result. If the sign in is NOT successful the returned task will throw an exception notifying your app of the failed result.
NOTE Most common cause for an exception in this scenario is the user, when presented with the Google Sign In Screen Overlay, opted not to choose any account and clicked the cancel option in the modal.
NOTE THIS GmailAuthException
SCENARIO WILL MOST LIKELY BE THE USER EXPERIENCE FLOW THE FIRST TIME A USER SIGNS IN TO YOUR APP
EXAMPLE GMAIL LOGIN IMPLEMENTATION
class GmailInboxFragment : Fragment() {
private lateinit var gmailClient: GmailClient
//...Instantiate GmailClient in one of the lifecycle methods
// Attempt to login
fun login() {
gmailClient.login()
.addOnSuccessListener {
}.addOnFailureListener { e ->
if (e is GmailAuthException) {
startActivityForResult(e.signInIntent, e.requestCode)
} else {
//Set error display
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
gmailClient.onAccountAuthorizationActivityResult(requestCode, resultCode, data)
.addOnSuccessListener { signInAccount ->
}.addOnFailureListener {
//Set error display
}
}
}
Once a user has successfully signed in we are good to go! We can now move on to retrieving emails from a signed in user.
Verifying Gmail Log In And Retrieving Already Signed In Users¶
So as not to constantly bombard users with a typical sign in flow, we provide the verify()
function on the GmailClient
. The verify()
call returns a Task<Boolean>
. This boolean result returnes either true
or false
. If the result is false
then this indicates that the sdk has no record of a signed in user. As a result you must take the user through the original sign in flow mentioned in the previous section.
In the event, the result returns a true
value, this indicates that we have an account signed in with Google. In which case, you can call credentials()
on the GmailClient
. The credentials()
call returns a Task<GoogleSignInAccount>
. This will silently fetch the signed in user’s account and return it to the caller via the Task
.
EXAMPLE GMAIL VERIFY AND CREDENTIALS IMPLEMENTATION
private fun verifyUser() {
gmailClient.verify()
.addOnSuccessListener {
}.addOnFailureListener {
}
}
Logging Out of Gmail¶
Users may log out of gmail via the client’s logout()
function. The logout function will return a Task<Boolen>
. This function completes 2 objectives. First, it signs the currently signed in user out from Gmail/Google SDK. This means that the user can no longer be “silently” signed in to Google. The next time the verify()
is called a false
result should be returned via the Task
return object. This also means that the credentials()
call will not return a GoogleSignInAccount
result, but instead throw an exception (not unlike the initial user flow for sign in we covered before). Secondly, the logout()
call will clear any cached “date” threshold we use for email searching. You should never receive a Boolean
result of false
for the signout task. It will always either be true or throw an exception in the unlikely event of an error.
EXAMPLE GMAIL LOGOUT IMPLEMENTATION
private fun logoutUser() {
gmailClient.logout().addOnSuccessListener {
}
}
Reading Messages And Getting Results¶
After a user has been signed in to their Gmail Account, we can now fetch their emails and find any receipts they may have stored in their email. Before we initiate a search, we want to make sure we have properly configured the GmailClient
. All sdk email clients have properties that can be configured to optimize searching. Here is a list of the following properties
Property Name | Type | Default Value | Client Function | Description |
---|---|---|---|---|
dayCutoff | Int | 14 | dayCutoff(int days) | Maximum number of days look back in a users inbox for receipts |
filterSensitive | Boolean | false | filterSensitive(boolean filterSensitive) | When set to true the sdk will not return product results for products deemed to be sensitive i.e. adult products |
subProducts | Boolean | false | subProducts(boolean subProducts) | Enable sdk to return subproducts found on receipts under parent products i.e. “Burrito + Guacamole <- Guac is subproduct” |
countryCode | String | “US” | countryCode(String countryCode) | Helps classify products and apply internal product intelligence |
Once the client is configured then we are ready to start parsing emails. On the GmailClient
call messages(@NonNull Activity activity)
to begin the message reading. This call returns a Task<List<ScanResults>>
. The calling of this function completes a series of tasks internally, before potentially returning a List<ScanResults>
. Upon a successful execution, the task will emit a result of List<ScanResults>
. If no results were able to be found, then the list will be empty. If results were found then each item in the list will represent a successfully scanned receipt. Please use the ScanResults data to display information to users or use for internal use.
EXAMPLE GMAIL READ MESSAGES
fun messages() {
client.messages(requireActivity())
.addOnSuccessListener { results ->
}.addOnFailureListener {
}
}
GmailClient Destroy Client¶
We always want to make sure we are adhereing to any component’s lifecycle. Therefore, it is very important to call destroy within the component. This will clean up any pending calls, and allocated resources.
override fun onDestroy() {
super.onDestroy()
client.close()
}