Background:
At my last job, I was given a Flic button to use as I saw fit – at first, after looking up what they cost (around $30) I was skeptical if I could get that kind of value of what was basically just a remote button for my smartphone. However, after playing around with it for a bit, I quickly realized that the real value in the Flic button was not the built-in integrations, but the fact that they allow extending its functionality by the use of Android “Intents”, which are a way for different Android Apps and services to communicate across the system. For those technically inclined, being able to use Android Intents as an action when the Flic Button is pressed means that you can basically tie the Flic button to any other Android App, service, or HTTP request, through the use of an automation app like Tasker or Automagic. For me, this opened a world of opportunities.
Flic + Automagic + Toggl
I was so impressed with Flic and their service, that I ended up purchasing a Flic button for my home. This post is about my favorite use of it – as a start/stop/resume button for the time tracker I use, Toggl. I did this by creating a very custom flow in an Android automation app called Automagic. Through the flow I set up, if I press the Toggl button my Automagic flow and code will execute, which ends up doing a few different things depending on the state of my Toggl time tracker:
- If…
- Case A: I have an existing time entry already started / in progress
- The Flic button acts as a “stop timer” button and stops the current timer and time entry
- Case B: My Toggl timer is not running, but my flow is set to “resume” mode and there is a recent time entry
- The Flic button “resumes” my last time entry. Toggl doesn’t really have a true “resume” function – this basically starts a duplicate entry, which Toggl is smart enough to combine with the previous in reporting
- Case C: My Toggl timer is not running, but my flow is set to “new” mode and/or there is no recent time entry
- The Flic button “starts” a brand new timer going, with the description set to generic “Working Time”
- Case A: I have an existing time entry already started / in progress
- For the creation of new time entries or resumes, my flow passes additional attributes, such as a tag of “API”, and “created_with” equal to “Flic + Automagic + API” – this way I can easily identify later on which timer entries were created with the button and which were created manually
Edit 2/26/2019: I’ve updated my flow and the code in my Github gist to also correctly use the same project ID that was used in the last time entry if resuming an entry. Toggl is not great at grouping entries together unless you explicitly tell it they had the same project ID.
Demonstration Video:
Setting up Flic to broadcast a “global intent”
In order for Automagic to be able to know about the button click from Flic, we need to need to somehow pass an even from Flic to the Automagic app. The Android OS actually has something baked into it for this type of cross-app communication – an “intent”.
Basically, you can set the Flic app to emit a global message that the Automagic app can listen in on, by having it emit an intent with the “Send Intent” action. You can set this up in Flic by doing the following:
- Open the flic app and tap the button you want to add the intent to
- Remove any existing action attached to the trigger you want to use (“Click”, “Double Click”, etc.).
- Tap the trigger name to start selecting an action
- Now, in the actions menu, find “Tools” in the bottom of the menu, click it, and then find “Send Intent” as the action
- Now you can create a completely custom intent that Flic will emit when it is triggered. It is up to you what values you use; just make sure they match what you are listening for / filtering on in the Automagic app or wherever you are listening for the intent
- Make sure to hit “Save Action”!
I created a video that walks through these steps, creating a custom intent with an action string of “globalflicintent-home”, which corresponds with my Automagic code. You can see it below:
The Automagic Flow
If you want to use my flow, you will have to make one minor modification. I have redacted my Toggl authentication token (for obvious reasons), so you will need to generate your own token and paste it as the string that will become the global variable called “global_togglAuth” in “set togglAuth to Home”. Note that you are not pasting your API token, you should be pasting the Base64 encoded string of either “[YOUR_API_TOKEN]:api_token” or “[USERNAME]:[PASSWORD]”. See this page for details. Optionally:
- Change the string on the first step to match whatever the Android Intent is that you are sending from Flic.
- Change step 3, toggl_action=’resume’ to toggl_action=’new’ to have the flow create a brand new time entry if the timer is stopped, instead of resuming the most recent previous time entry
I have uploaded the XML export of my Automagic flow and posted it as a Github gist here.
Here is the visual representation of the flow (click the image for the full size zoom-able version):
Reminders / “Gotchas” about Automagic Scripting
I really like Automagic as an automation app, particularly because it supports custom scripting. However, although the scripting language looks and functions a lot like JavaScript, it is not, and there are some mistakes that JS programmers are like to make that tripped me up a little and are worth mentioning:
- Checking if a variable is set / defined / not null – with getValue();
- Let say I want to check if a variable is defined in order to determine how to continue the flow. The easy way to this is with getValue() which is described the docs as follows:
-
Object getValue(String name, Object default)
Returns the value of the variable namedname
or returnsdefault
when the variable is undefined or null.
-
- It is easy to glance over this and miss something important… I’ll show you the mistake I made:
-
myVariable = 'Hello World'; isDefined = getValue(myVariable,false); if (isDefined!=false){ // Do something }
-
- In the above code, isDefined actually evaluates to false and the IF statement will never evaluate to true! The key here is that getValue expects a string as the first parameter, which should be the name of the variable, not the variable itself. Here is the correct code:
-
myVariable = 'Hello World'; isDefined = getValue('myVariable',false); if (isDefined!=false){ // Do something }
-
- Let say I want to check if a variable is defined in order to determine how to continue the flow. The easy way to this is with getValue() which is described the docs as follows:
- You can’t initialize an object variable the normal way
- If I want to create a new variable that is equal to an object, my instinct is to do something like this:
-
myObj = {"Lorem" : "Ipsum"};
-
- This is wrong! This will not work with Automagic. Here is how to construct this in Automagic
-
myObj = fromJSON('{}'); myObj['Lorem'] = 'Ipsum';
-
- Using the above syntax, you can even create nested objects, and then later, use toJSON() to convert to a JSON string to use in a POST HTTP request.
- If I want to create a new variable that is equal to an object, my instinct is to do something like this:
- If you want to check the length of an array, use length(arrVar), not arrVar.length
- Use bracket notation to retrieve object values, not dot notation – e.g. myObj[‘myKey’] not myObj.myKey.
- To use variables in non-script actions (e.g. an HTTP request) wrap the variable name in {} – this is actually easy to remember since this is how many templating engines and languages handle combining strings with variables.
Reminder to self: How to fix super slow typing when using USB OTG plugged into smartphone
I was editing this Automagic script on my smartphone, and plugged in a USB keyboard (via OTG) to make editing easier. However, every single keypress took forever and lagged terribly. The problem was instantly fixed when I changed the active Android Keyboard from “Gboard” (Google’s distributed keyboard app) to the default “Android Keyboard (AOSP)”. My guess is that Google’s fancy keyboard app was trying to spellcheck the entire script field and was not detecting a plugged in USB keyboard, whereas the stock keyboard maybe has lower level access and knew to butt out of the “conversation”. Regardless, it was an easy fix!