add blog posts from ddl site

This commit is contained in:
James Fenn
2019-08-09 15:13:07 -04:00
parent ab8d267429
commit c135506d10
3 changed files with 563 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
---
{
title: "Introduction to Android: Contexts, Intents, and the Activity lifecycle",
description: 'A basic overview of the main components of an Android app and how they interact with each other and the Android system',
published: '2019-03-18T22:12:03.284Z',
author: 'fennifith',
tags: ['android'],
attached: []
}
---
This is a basic summary of the different components of Android and what they can be used for. It
is written with the assumption that you already have basic knowledge about Android development,
such as Java programming and the basic construction of a simple Android app (e.g. `Activity`
classes, the `AndroidManifest.xml`, and layout files).
If you are completely new to Android development, I would recommend following through Android's
["Build your first app"](https://developer.android.com/training/basics/firstapp/) tutorial before
reading this article.
## Contexts
In Android, a `Context` is a general class that... facilitates your app's interaction with the
Android system? I'm not sure how to best explain it, but it essentially gives you access to
everything in your application from string resources and fonts to starting new Activites.
The `Application`, `Activity`, and `Service` classes all extend `Context`, and `View` classes
all require an instance of one to be displayed (you can obtain this instance by using the
View's `.getContext()` method). This allows you to access information such as the device's
screen orientation, locale, and obtain assets particular to this information. For example,
locale-specific string resources (which are commonly defined in `res/values/strings.xml`) can
be obtained by calling `context.getString(R.string.string_name)`, while Drawables (a type of
image asset) can be obtained using `context.getDrawable(R.drawable.drawable_name)`.
The `R` class that is used to obtain these resources is a collection of static identifiers
that is automatically generated by Android Studio at build/compile-time.
For more about translating strings, see the
["Localize your app"](https://developer.android.com/guide/topics/resources/localization) guide
in the Android Developer Documentation.
For more about Drawables and other image assets, see
["Drawable resources"](https://developer.android.com/guide/topics/resources/drawable-resource.html).
A general overview of app resources can be found
[here](https://developer.android.com/guide/topics/resources/providing-resources).
## Intents
Every component inside of an Android app is started by an `Intent`. Components declared in an
app's manifest can typically be invoked from _anywhere in the system_, but you can define
intent-filters to declare that they should be started by a specific type of "thing". Your app's
main activity has a filter like `android.intent.category.LAUNCHER`, which is how the home screen
knows to display and launch _that specific activity_ when the user opens your app.
Assuming that you have an active `Context`, you can start other activities inside your application
by firing an intent that references the classes directly, like:
```java
context.startActivity(new Intent(context, ActivityClass.class));
```
This call to `startActivity` sends the `Intent` to the Android system, which is then in charge of
creating and opening the activity that you have specified.
### Starting an Unknown Activity
You do not always need to specify an explicit class to start a new activity, though. How would your
app start an activity in another application? You don't know what its class name is, and if you did,
you likely wouldn't be able to reference it since it isn't a part of your app. This is where
intent-filters come in: they allow you to start an activity without explicitly stating which activity
should be launched. Take a look at the following intent:
```java
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("content://..."), "image/*");
context.startActivity(intent);
```
This intent will open any activity on the device that claims it is able to display image files from
a URI. The first (or most prominent) activity that the Android system finds with a filter that contains
the `android.intent.action.VIEW` action and accepts `image/*` data will be launched by the system.
If you want to let the user choose which activity is launched that meets the criteria, you could open
a "share menu" with `context.startActivity(Intent.createChooser(intent, "Open with..."));`. Part of the
reason that Android's share menus are notorious for being
_[so ridiculously slow](https://issuetracker.google.com/issues/68393945)_ is that in order to display
these lists, it has to query every single activity on the device asking "will you accept this Intent?"
to make a list for the user to choose from.
### Sending Data to Activities
In order for an Activity to have any dynamic functionality, you will need to have some way of sending
information to it. When you create an Intent to a new Activity, you should _never_ have an instance of
the created activity, as it is created and managed separately by the system. While under normal
circumstances this may not present any obvious issues, there are situations where this would not be possible
(for example, starting an activity in a different process or task hierarchy). However, you still need a
reliable way to tell an activity what to display while abiding by the laws of the system. There are two
main ways of doing this, both of which have their own advantages and disadvantages:
#### 1. Create your own state / data provider.
This indirectly relies on having access to an instance of the activity, though it should not fail if
it does not obtain an instance; rather than relying on the started activity being created, it acts
as more of a general solution to managing the data or state across your application.
The [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/index.html)
suggest to use a [LiveData](https://developer.android.com/topic/libraries/architecture/livedata)
observable data class for this purpose, which allows you to persist a set of information across
your entire application and notify other parts of your app when it is changed. While this is a
very robust solution that will make your application much easier to maintain in the long run,
it can be a little bit complicated, especially if you are writing a simple application that
only needs to manage a small amount of information.
#### 2. Use Intent extras.
The other, much simpler method of transferring data between activities is to simply include the
data in the Intent. This data will be passed with the Intent to the Android system when it starts
the activity, which will then give it to the new Activity once it has been created. For example:
```java
Intent intent = new Intent(context, ActivityClass.class);
intent.putExtra("com.package.name.EXTRA_MESSAGE", "Hello world!"); // the data to send
context.startActivity(intent);
```
Now, when the new Activity is created (inside the `onCreate` method), you can obtain the provided
data as such:
```java
Bundle extras = getIntent().getExtras();
if (extras != null) {
String message = extras.getString("com.package.name.EXTRA_MESSAGE");
// message == "Hello World!"
}
```
Of course, this has its restrictions; since the data is passed to the system, there is a size limit
on the amount of data that you can pass through an Intent - most primitive types and small data
structures / serializable classes will be fine, but I would recommend against passing heavier classes
such as Bitmaps or Drawables.
For more information about Intents, there is a more descriptive summary of them in the
[Android Developer Documentation](https://developer.android.com/reference/android/content/Intent).
### Note: More about Contexts
When your application is opened by the system, the first components to be created will be the
`Application`, then the `Activity` - which will have a different Context from the Application
component. If you Activity contains a layout with a set of views, the context that a view has
can probably be cast to an Activity (like `(Activity) view.getContext()`) without any problems,
but it... isn't a very good idea to assume this, as there are weird situations where this might
not work.
If your app needs to have a global "thing" shared between all of its components, or if you need
to notify the parent activity of an event occurring in a view, then it is best to put that inside
of your app's `Application` class (which should be referenced from the manifest) and have your
Activities and other parts of your application look there for the information. The Application
class can be obtained from any `Context` instance by calling `context.getApplicationContext()`.
## Activity Lifecycle
Activities are big and complicated things, and many events can occur during their use as a result
of user interaction or just weird Android memory-saving things. However, it is important to know
what state of the lifecycle your Activity is in when performing a task as things can go very wrong
if you try to do something at the wrong time, or fail to stop at the right time.
You are probably familiar with the `onCreate()` method - this is the first event to happen in the
Activity lifecycle. Here, you declare and inflate your Activity's layout and set up the UI. After
this, `onStart()` and `onResume()` are called once your layout becomes visible and the user
can interact with it. From here, a few different events can occur...
Let's say that another Activity comes into the foreground (but this activity is still visible behind
it; imagine a popup or something that the user will return to your app from).
- `onPause()` called; stop doing anything significant - playing music or any continuous task not running
in another component (like a `Service`) should be ceased.
Then, if the user returns to the activity...
- `onResume()` called; resume whatever was paused previously
If the user leaves your activity completely, then you will get:
- `onPause()` called; probably stop doing stuff maybe
- `onStop()` called; okay, REALLY stop doing stuff now
Then, if the user navigates back to your activity...
- `onRestart()` called
- `onStart()` called
- `onResume()` called
When the application is completely closed by the user, then you will receive:
- `onPause()` called
- `onStop()` called
- `onDestroy()` called
A more comprehensive overview of the Activity lifecycle can be found
[here](https://developer.android.com/guide/components/activities/activity-lifecycle).
## More...
What about tasks that you want to exist beyond the Activity lifecycle? Maybe you want music to
keep playing after the user leaves the app, or you just want to perform a short action without
opening an activity when a certain event occurs. There are two other components that can receive
intents for this purpose: `Service` and `BroadcastReceiver`.
### Services
Services can run in the background without being attached to a user interface for longer periods
of time, for tasks such as playing music, downloading large files, or other potentially lengthy
operations that shouldn't be terminated when the user leaves the app.
See: [Service documentation](https://developer.android.com/reference/android/app/Service).
### Broadcast Receivers
A broadcast receiver can be seen as more of an "event" that occurs once and is over. They can run
independently from a UI, the same a Service, but only for a short period of time (I believe they are
terminated by the system after ~10 seconds - citation needed). However, they are given a `Context`,
and can fire an intent to start other components of the app if needed.
Broadcast receivers are a little special in that they don't have to be declared explicitly in the
`AndroidManifest.xml`. While Activities and Services must be declared in order to be used, broadcast
receivers can be registered dynamically when your application is running, and can be unregistered
again when they are no longer needed.
See: [BroadcastReceiver documentation](https://developer.android.com/reference/android/content/BroadcastReceiver).
## Fin
That's all for now! This was not a very thorough overview of Android development, and I feel like
I left a lot of holes and exceptions to what I mentioned here, but hopefully it is useful to someone.

View File

@@ -0,0 +1,82 @@
---
{
title: "Joining Freenode IRC: A Guide",
description: 'Basic (but detailed) instructions for setting up a Freenode IRC account through various clients',
published: '2019-03-06T22:12:03.284Z',
author: 'fennifith',
tags: ['irc'],
attached: []
}
---
Internet Relay Chat is a difficult thing to get used to, especially for people who were born into this world of full graphical interfaces and messaging web apps that handle user interaction seamlessly. IRC is a little bit different, though it still has a lot of the functionality that conventional messengers do: group chats / channels, admin (operator) permissions, user ban lists, private messages, and _quite a bit more_. However, a lot of this functionality may seem obscured to new users, as most IRC clients don't have the fancy menus, dropdowns, or simple toggles and check box elements that are often taken for granted - they use more of a command line-like interface, having users remember the commands to execute a specific action instead, like `/motd` or `/whois fennifith`.
## Choosing a Client
The first thing that you'll want to do before logging into freenode is choose an IRC client to connect with. I've compiled a list of the ones that I have tried below.
- **Android**
- [Revolution IRC Client](https://play.google.com/store/apps/details?id=io.mrarm.irc)
- [Riot IM](https://about.riot.im/)
- [AndroIRC](https://play.google.com/store/apps/details?id=com.androirc)
- [IRCCloud](https://play.google.com/store/apps/details?id=com.irccloud.android)
- **Linux**
- **CLI**
- [WeeChat](https://weechat.org/)
- [Irssi](https://irssi.org/)
- **GUI**
- [HexChat](https://hexchat.github.io/)
- [XChat](http://xchat.org/)
- **Windows**
- [HexChat Windows](https://www.microsoft.com/en-us/p/hexchat/9nrrbgttm4j2)
- **Web**
- [Riot IM](https://riot.im/app/)
- [Freenode Webchat](https://webchat.freenode.net/)
- [Kiwi IRC](https://kiwiirc.com/)
- [The Lounge](https://demo.thelounge.chat/)
## Connecting to Freenode
Connect to the freenode servers by specifying `chat.freenode.net` as the server, and either port `6697` if your client supports SSL/TLS connections, or `6667` if it does not. Many clients have a preset option for connections to freenode, for example in `irssi` you can simply type `/CONNECT Freenode` to connect to a freenode server without needing to configure anything else.
For a more detailed explanation of connecting to freenode, [Freenode's documentation](https://freenode.net/kb/answer/chat) might be useful.
## Registering a Nickname
First, you'll want to choose a nick. This will be something that all users will see and address you by, so it should be easy to remember. If you have a twitter or github handle, it is best to make it as similar as possible to that in order to stay consistent. In the following steps, replace the information surrounded by `<>` with the relevant data.
1. Send the command `/nick <username>`, followed by a message to `NickServ` by running `/msg NickServ REGISTER <password> <email@example.com>`.
2. You should receive an email with another command to run, along the lines of `/msg NickServ VERIFY REGISTER <username> <code>`. This will confirm your identity to freenode and reserve the nickname for your use.
3. If you plan to use your account from multiple devices simultaneously, you will need to have one username for each. You can join them to your current account by:
- Setting your nick to a new username: `/nick <username2>`
- Identifying with your existing credentials: `/msg NickServ IDENTIFY <username> <password>`
- Grouping the nick with your account: `/msg NickServ GROUP`
Each time you reconnect to freenode, you will need to log in. [Freenode's registration docs](https://freenode.net/kb/answer/registration) have more information on this, but it is possible to simply run `/msg NickServ IDENTIFY <username> <password>` each time you connect.
## Joining a Channel
On most IRC servers, you can run `/list` to display a list of all of the channels on the server that you can join. However, as freenode has just shy of 50000 channels, this command will generate quite a large output that may not be to your liking. Two options here: you can either use a web index, such as [irc.netsplit.de](http://irc.netsplit.de/channels/?net=freenode), to view a list of channels in a more usable format, or you can use freenode's [alis tool](https://freenode.net/kb/answer/findingchannels) to search through the list with a query such as `/msg alis LIST programming`. Alis has quite a few other options to trim down the search results, and I reccomend taking a look at `/msg alis HELP LIST` before you start scrolling through 1000+ search results to look for a particular topic.
## General Use
By now, you've probably gotten a decent feel for how IRC chat works - most commands handle faulty input fairly gracefully and let you know what they're doing and how to use them properly. Most commands and usernames are case insensitive, and help can usually be found by simply adding `help` after the root command, ex: `/msg NickServ HELP VERIFY`. If you haven't come across them already, here is a list of various useful commands and what they do:
- `/info`: display information about the server
- `/names`: show the usernames of members in the current channel
- `/whois <username>`: looks up information about a particular user's connection
- `/msg <username> <message>`: sends a private message to a user
- `/join <channel>`: joins a particular channel
- `/me <action>`: invoke a virtual action, such as `/me takes a humongous bite of their pie` to create a notice such as "fennifith takes a humongous bite of their pie"
- `/describe <username> <description>`: similar to `/me`, using the username of someone else on the network, ex: `/describe steve012 crashes through the wall`
- `/notify <username>`: tells the server to send you a notification when another user logs on
- `/ping <username|channel>`: displays information about the distance between your computer and other users on the network
- `/quit <message>`: quits the server, sending a final comment to any chats you may be involved with
More commands, along with basic descriptions of how they work and examples of their use, can be found [here](https://www.livinginternet.com/r/r.htm).
## Policies
Last, but certainly not least, I recommend that you scroll through [freenode's policies](https://freenode.net/policies) to get an idea of the purpose of the project and what is deemed acceptable use of their servers. Most channels have their own code of conduct to go along with these policies, which you should review to make sure that you aren't unknowingly violating any rules when contributing to a discussion. The [channel guidelines](https://freenode.net/changuide) also list more definitions of what is considered to be acceptable behavior on IRC (and really any social network).
And, most importantly, have fun!

View File

@@ -0,0 +1,251 @@
---
{
title: "Continuous Integration with Travis CI for Android",
description: 'An in-depth tutorial explaining how to set up Travis CI to deploy signed builds to Google Play. Among other things',
published: '2018-11-14T22:12:03.284Z',
author: 'fennifith',
tags: ['android', 'ci'],
attached: []
}
---
Last week, I started setting up continuous integrations for some of my projects. The basic idea of a continuous integration is that you have a server to build your project on a regular basis, verify that it works correctly, and deploy it to wherever your project is published. In this case, my project will be deployed to the releases of its GitHub repository and an alpha channel on the Google Play Store. In order to do this, I decided to use [Travis CI](https://travis-ci.com/), as it seems to be the most used and documented solution (though there are others as well). Throughout this blog, I will add small snippets of the files I am editing, but (save for the initial `.travis.yml`) never an entire file. If you get lost or would like to see a working example of this, you can find a sample project [here](/redirects/?t=github&d=TravisAndroidExample).
A small preface, make sure that you create your account on [travis-ci.com](https://travis-ci.com/), not [travis-ci.org](https://travis-ci.org/). Travis previously had their free plans on their .org site and only took paying customers on .com, but they have since begun [migrating all of their users](https://docs.travis-ci.com/user/open-source-on-travis-ci-com/) to travis-ci.com. However, for some reason they have decided _not to say anything about it_ when you create a new account, so it would be very easy to set up all of your projects on their .org site, then (X months later) realize that you have to move to .com. This isn't a huge issue, but it could be a little annoying if you have _almost 100 repositories_ like I do which you would have to change (though I have only just started using Travis, so it doesn't actually affect me). Just something to note.
## Step 1: Start your first build
There are a few basic things to do in order to build your project. Assuming that you have already [set up your account](https://docs.travis-ci.com/user/tutorial/) and authenticated it with your GitHub, you will next want to create a file named `.travis.yml` in your project's root directory. One thing to keep in mind here is that the YAML format in this file is heavily dependent on whitespace; tab characters are invalid, indents must be made only in spaces, and a sub-section or parameter **must** be indented or it will not be treated as such. To start, let's write a basic file that should properly build most up-to-date Android projects.
```yml
language: android
android:
components:
- tools
- platform-tools
- build-tools-28.0.3
- android-28
- extra-google-google_play_services
- extra-google-m2repository
- extra-android-m2repository
jdk:
- oraclejdk8
before_install:
- chmod +x gradlew
```
You will want to update the `android` and `build-tools` versions to match the respective values in your project's `build.gradle` file, and `extra-google-google_play_services` can be omitted (it will speed up build times) if you are not using it. The same goes for the `jdk`. Note the `before_install` section; statements placed there are executed before your project is built or installed (side-note: you will want to make sure `gradlew` and `gradle/wrapper` are in your version control; Travis uses them to build your project).
Now, when you commit this file to your repository (the branch should not make a difference), Travis should build your project and notify you of the result.
## Step 2. Signing APKs
So Travis _can_ successfully build your APK, but that itself is not very useful. It can do something with debug APKs, sure, but deploying them won't be very useful as they won't be under the same signature, and users won't be able to update from the existing application. So... we need a way to sign the application using an existing keystore that Travis has access to.
> LET'S UPLOAD OUR KEYSTORE TO GIT!
Not a bad idea. This will easily give Travis the ability to sign our APK. Isn't there some reason that you shouldn't share your keystore online, though, maybe something about "malicious developers and companies can use it to update your application without your knowledge"? Weeeelll why don't we use Travis's built-in encryption service? This will give you an encrypted file (like `key.jks.enc`) that you can safely add to git, and add a command to the `before_install` section in your `.travis.yml` to decrypt it.
> But... can't someone just look in your `.travis.yml`, get the command, and use it to decrypt your file?
No, they can't. This is because the values passed to the command are two [environment variables](https://docs.travis-ci.com/user/environment-variables/#defining-variables-in-repository-settings) which are stored only on Travis. As long as you _don't_ check the "show value in log" box when you create an environment variable, they will never be output anywhere in your build logs, and nobody will be able to see them or know what they are.
If you are worried about security (or if you aren't worried enough), I highly recommend that you read [Travis's documentation](https://docs.travis-ci.com/user/best-practices-security/#Steps-Travis-CI-takes-to-secure-your-data) on best practices regarding secure data.
### Part A. Encrypting files
You can go about this two ways: a difficult way, or a difficult way. You can either install [Travis's CLI tool](https://docs.travis-ci.com/user/encrypting-files/) for the sole purpose of logging in, encrypting your file, and setting its environment variables, or you can just do it yourself. I will provide instructions for both. Do what you like.
Note that if you want to automatically deploy your builds to Google Play, you may want to come back here and go through the exact same process later on, so you might want to skip this for now. If you don't, or want to do it twice anyway... carry on...
#### Using Travis's CLI
First, install it. Assuming you have Ruby set up, you'll want to run `gem install travis`. Since not everyone has Ruby set up, [here are their installation instructions](https://www.ruby-lang.org/en/documentation/installation/). A bit of a pain for something that you can just write yourself in my opinion, but hey, anything to avoid writing more code.
After that, you'll want to log in. Run `travis login` and it will walk you through it. Note: (related to the preface at the start) no matter what site you are using when you use the Travis CLI, you should append either `--org` or `--com` to **every command** to specify which site it should use.
Now, find your keystore. Place it in your root directory. The CLI detects git repos to determine what project you want to modify, so this is necessary. Do not add it to git. That is bad and not good. Don't do that.
Assuming you have named your keystore `key.jks`, you will want to run `travis encrypt-file key.jks --add`. This will encrypt the file, add the command to your `.travis.yml`, and upload the environment variables all at once. You can then add `key.jks.enc` to git, commit and push, and it will be available to your next build.
Side-note: if your keystore is a `.keystore` file, it shouldn't make a difference - just replace `key.jks` with `key.keystore` (or whatever it is named) whenever it appears.
#### Doing It Yourself
Pick a key and a password. They shouldn't be excessively long, but not tiny either. Do not use special characters. In this example, I will use "php" as the key and "aaaaa" as the password.
Add them to Travis CI as environment variables. You can do this by going to your project page in Travis, clicking on "More Options > Settings", then scrolling down to "Environment Variables". I will name mine "enc_keystore_key" and "enc_keystore_pass", respectively.
Now, time to encrypt the file. Run this command in the terminal:
```bash
openssl aes-256-cbc -K "php" -iv "aaaaa" -in key.jks -out key.jks.enc
```
Now, you will want to add a line to decrypt the file in `before_install` of your `.travis.yml`. You should not pass your key/password here, as this file will be pushed to git, and that would be bad. Instead, we will reference the environment variables.
```yml
before_install:
- ...
- openssl aes-256-cbc -K $enc_keystore_key -iv $enc_keystore_pass -in key.jks.enc -out key.jks -d
```
That's it! Push your changes to `.travis.yml` as well as `key.jks.enc`, and Jekyll should build your project.
### Part B. Dummy files
This isn't entirely necessary, but you can use some fake "dummy" files to add to version control alongside the "real" encrypted ones. When Travis decrypts your encrypted files, they will be overwritten, but otherwise they serve as quite a nice substitute to prevent anyone from getting their hands on the real files (and to prevent you from uploading the real ones by accident). You can find a few (`key.jks`, `service.json`, and `secrets.tar`) in the sample project [here](/redirects/?t=github&d=TravisAndroidExample).
### Part C. Signing the APK
Now we want to actually use the key to sign our APKs. This requires a few changes to our app's build.gradle. Specifically, we need to specify a `signingConfig` that ONLY exists on Travis - we don't want our local builds (or the builds of other contributors) to be affected by this. Luckily, not only can we read environment variables from our `build.gradle` file using `System.getenv`, Travis automatically creates a nice "CI" variable to tell us that the build is happening in a Continuous Integration, so why don't we use that.
Full credit, this solution was taken from [this wonderful article](https://android.jlelse.eu/using-travisci-to-securely-build-and-deploy-a-signed-version-of-your-android-app-94afdf5cf5b4) that describes almost the same thing that I have been explaining since the start of this article.
I'll create three environment variables that will be used here: the keystore password as "keystore_password", the keystore alias as "keystore_alias", and the alias's password as "keystore_alias_password". Note that special characters cannot be used in these either.
```gradle
android {
...
signingConfigs {
release
}
buildTypes {
release {
...
signingConfig signingConfigs.release
}
}
def isRunningOnTravis = System.getenv("CI") == "true"
if (isRunningOnTravis) {
signingConfigs.release.storeFile = file("../key.jks")
signingConfigs.release.storePassword = System.getenv("keystore_password")
signingConfigs.release.keyAlias = System.getenv("keystore_alias")
signingConfigs.release.keyPassword = System.getenv("keystore_alias_password")
}
}
```
Of course, Travis isn't currently building a release variant (I think it defaults to `./gradlew build`), so this `signingConfig` won't be applied. We need to change that. Add the following to your `.travis.yml`...
```yml
script:
- ./gradlew assembleRelease
```
Now it will create a proper release using these signing configs. Push everything to git and it should build a properly signed APK. Yay.
## Step 3. Deploying to github releases
This part is fairly simple, as Travis provides its own deployment functionality for this purpose. According to [their documentation](https://docs.travis-ci.com/user/deployment/releases/), for the bare minimum functionality all that you will need is to add the following to your `.travis.yml`...
```yml
deploy:
- provider: releases
api_key: "GITHUB OAUTH TOKEN"
file: app/build/outputs/apk/release/*
file_glob: true
skip_cleanup: true
on:
tags: true
```
Now, you _could_ follow this exactly and place your GitHub token directly in your `.travis.yml`, but that's just asking for trouble. Luckily, you can use MORE ENVIRONMENT VARIABLES! Enter your API key with the name ex. "GITHUB_TOKEN", and write `api_key: "$GITHUB_TOKEN"` instead.
This should now create a release with a built (and signed) APK each time there is a new tag. Fair enough; all you have to do for it to deploy is create a new tag.
### Part A. Creating tags
What if you're lazy like me, though? What if you want to create a new release on each push to the master branch? (I have two branches in most of my projects, `develop` and `master`, for this purpose - only the commits currently in production are in the `master` branch)
A simple modification to the `on` section of the previous snippet does the trick.
```yml
deploy:
...
on:
branch: master
```
Well, it almost does the trick. The thing is, since we haven't created a tag, Travis doesn't know what version number we want to use. It just creates a new release using the commit hash as a title. That isn't very good. I wonder if we could somehow get the version number from our build.gradle file and use that instead...
### Part B. Version numbers
Let's write a gradle task to print our version number! Place the following in your app's `build.gradle`.
```gradle
task printVersionName {
doLast {
println android.defaultConfig.versionName
}
}
```
Now when you run `./gradlew :app:printVersionName`, your version name should be printed in the console. Now all we have to do is use this in our deployment.
Just as there is a `before_install` section of our `.travis.yml`, there is also a `before_deploy`. As such, we can add the following:
```yml
before_deploy:
- export APP_VERSION=$(./gradlew :app:printVersionName)
```
This creates an environment variable ("APP_VERSION") containing our app's version name, which we can then reference from the actual deployment as follows...
```yml
deploy:
- provider: releases
api_key: "$GITHUB_TOKEN"
file: app/build/outputs/apk/release/*
file_glob: true
skip_cleanup: true
overwrite: true
name: "$APP_VERSION"
tag_name: "$APP_VERSION"
on:
branch: master
```
Yay! Now we have fully automated releases on each push to master. Because of the `overwrite` parameter, it will overwrite existing releases if the version number has not been changed (a new release will be created if it has), so they will always be up to date.
## Step 4. Deploying to the Play Store
Travis doesn't have a deployment for the Play Store, so we will have to use a third party tool. I found [Triple-T/gradle-play-publisher](https://github.com/Triple-T/gradle-play-publisher/), which should work, except there isn't an option to deploy an existing APK without building the project. Not only would a deployment that requires building a project _twice_ be super wasteful and take... well, twice as long, [I ran into problems signing the APK](https://jfenn.me/redirects/?t=twitter&d=status/1061620100409761792) when I tried it, so... let's not. Instead, we'll modify the `script` to run the `./gradlew publish` command when a build is triggered from the master branch.
### Part A. Setup
Setup is fairly simple; just follow the directions in the plugin's readme. However, what should we do with the JSON file? PLEASE DO NOT ADD IT TO GIT. ANYONE WITH THIS FILE HAS ACCESS TO YOUR PLAY CONSOLE. WE'RE ENCRYPTING IT.
You can either encrypt it as a separate file, or you can put them both in a tar (`tar -cvf secrets.tar key.jks service.json`), encrypt that, and run `tar -xvf secrets.tar` once it has been decrypted. I am not sure if either will affect how secure they are. I have opted for the tar method as it gives me less things to keep track of.
### Part B. Publishing
Now we can modify the `script` section of our `.travis.yml` to run the `./gradlew publish` command when a build is triggered from the master branch. This can be done using the "TRAVIS_BRANCH" environment variable which Travis handily creates for us. In other words...
```yml
script:
- if [ "$TRAVIS_BRANCH" = "master" ]; then ./gradlew publish; else ./gradlew build; fi
```
This should build a signed APK and upload it to the Play Store whenever a push is made to the `master` branch, then deploy the same APK to GitHub if it was built successfully. Important to note that using this method, the build will also fail if it has failed to upload the APK to the Play Store - so it _might_ not be an issue with your project if it results in a failure unexpectedly.
### Part C. Changelogs
Now, gradle-play-publisher requires you to specify a changelog at `app/src/main/play/release-notes/en-US/default.txt` for it to publish an APK. What if we want to use the same changelog for GitHub releases? We'll add another line to the `before_deploy` section and GitHub deployment to do so.
```yml
before_deploy:
...
- export APP_CHANGELOG=$(cat app/src/main/play/release-notes/en-US/default.txt)
deploy:
- provider: releases
...
body: "$APP_CHANGELOG"
```
## Finish
Hopefully this blog has gone over the basics of using Travis to deploy to GitHub and the Play Store. In later blogs, I hope to also cover how to implement UI and Unit tests, though I have yet to actually use them myself so I cannot yet write an article about them.
If you would like to see a working example of all of this, you can find it in a sample project [here](https://jfenn.me/redirects/?t=github&d=TravisAndroidExample).