Skip to content

Commit

Permalink
Release Android app to Google Play store (#3)
Browse files Browse the repository at this point in the history
* Attempt to generate a signed APK

* Overcome release issues

* Access release key password from keychain

* Fix unmet dependency error

* Add About page and Contact info; close #1

* Add venmo link #1

* Change section order on About page

* Detect internet connection before making request

* Edit contents of about page

* Edit about page and restrict stations

* Fix default station issue

* Customize home screen icon
  • Loading branch information
s2t2 authored Jun 12, 2017
1 parent 7d14831 commit 1fb3201
Show file tree
Hide file tree
Showing 21 changed files with 480 additions and 91 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ android/keystores/debug.keystore

# ANDROID APP PEER DEPENDENCIES (NATIVE BASE NEEDS VECTOR ICONS):
android/app/src/main/assets/fonts

# ANDROID STORE
*.keystore
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ npm install

Setup a [react-native developer environment](http://data-creative.info/process-documentation/2016/07/22/react-native-android-dev-env-setup-from-scratch/).

Also [enable the gradle daemon](https://docs.gradle.org/2.9/userguide/gradle_daemon.html) to speed-up build times:

```` sh
touch ~/.gradle/gradle.properties && echo "org.gradle.daemon=true" >> ~/.gradle/gradle.properties
````

## Running Locally

### On Android
Expand Down
21 changes: 21 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
+ https://github.com/xgfe/react-native-datepicker/blob/7bed2921e5e6b1044f3bc8af05ed3d54c633c81f/style.js
+ https://facebook.github.io/react-native/docs/listview.html
+ https://facebook.github.io/react/docs/reusable-components.html#prop-validation
+ https://github.com/oblador/react-native-vector-icons/issues/480#issuecomment-304471943
+ https://github.com/airbnb/lottie-react-native/issues/38
+ https://facebook.github.io/react-native/releases/0.21/docs/linking.html
+ http://facebook.github.io/react-native/releases/0.42/docs/netinfo.html
+ https://facebook.github.io/react-native/docs/alert.html

## Styling and Design

Expand All @@ -63,6 +68,22 @@
+ http://nativebase.io/docs/v0.5.2/getting-started#installing-peer-dependencies
+ https://github.com/rnpm/rnpm

### Releasing

+ https://stackoverflow.com/a/21609757/670433
+ https://docs.gradle.org/2.9/userguide/gradle_daemon.html
+ https://www.garyshood.com/imageconvert/code.php
+ https://stackoverflow.com/questions/34329715/how-to-add-icons-to-react-native-app

## Moment

+ http://momentjs.com/guides/#/warnings/js-date/

## Android Store

+ https://facebook.github.io/react-native/docs/signed-apk-android.html
+ https://stackoverflow.com/questions/35935060/how-can-i-generate-an-apk-that-can-run-without-server-with-react-native
+ https://pilloxa.gitlab.io/posts/safer-passwords-in-gradle/
+ https://stackoverflow.com/questions/22681907/you-uploaded-an-apk-that-is-not-zip-aligned-error
+ https://developer.android.com/studio/publish/app-signing.html#releasecompile
+ https://www.youtube.com/watch?v=1aA949H-shk
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
A React-native mobile client for the [Next Train API](http://next-train-production.herokuapp.com/).

![a demonstration of an app being used on an android device. the user chooses a route by selecting an origin train station and a destination train station. the route gets saved to the user's favorites, and a corresponding link is added to the home-screen. the user then clicks on the route link from the home screen and is redirected to a list of upcoming trains.](demo.gif)

## [Contributing](/CONTRIBUTING.md)

## [Releasing](/RELEASING.md)

## [License](/LICENSE.md)
48 changes: 48 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Releasing

## Android

This document describes the process of releasing a new software version to the Android Store. Adapted from [source](https://facebook.github.io/react-native/docs/signed-apk-android.html).

### Prerequisites

#### Release Key

```` sh
cd android/app
````

Generate a release key called `next-train-ct-release-key.keystore` with an alias called `next-train-ct-key-alias`.

```` sh
keytool -genkey -v -keystore next-train-ct-release-key.keystore -alias next-train-ct-key-alias -keyalg RSA -keysize 2048 -validity 10000
````

Ensure it is not being tracked by version control.

Save it somewhere secure.

[Add it to your Mac OS Keychain](https://pilloxa.gitlab.io/posts/safer-passwords-in-gradle/):

+ Item Name: `next-train-ct-android-release-key`
+ Account Name: `mjr`
+ Password: HIDDEN

#### Home Screen Icons

Save icon images as `ic_launcher.png` in various directories within `/android/app/src/main/res/mipmap-*`:

+ 72*72 in mipmap-hdpi
+ 48*48 in mipmap-mdpi
+ 96*96 in mipmap-xhdpi
+ 144*144 in mipmap-xxhdpi.

### Releasing

Generate an APK:

```` sh
cd android && ./gradlew assembleRelease
````

Upload the APK file (`android/app/build/outputs/apk/app-release.apk`) to the [Google Play Developer Console](https://play.google.com/apps/publish/).
34 changes: 34 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,29 @@ def enableSeparateBuildPerCPUArchitecture = false
*/
def enableProguardInReleaseBuilds = false






def getPassword(String currentUser, String keyChain) {
def stdout = new ByteArrayOutputStream()
def stderr = new ByteArrayOutputStream()
exec {
commandLine 'security', '-q', 'find-generic-password', '-a', currentUser, '-s', keyChain, '-w'
standardOutput = stdout
errorOutput = stderr
ignoreExitValue true
}
//noinspection GroovyAssignabilityCheck
stdout.toString().trim()
} // source: https://pilloxa.gitlab.io/posts/safer-passwords-in-gradle/

def keychain_password = getPassword("mjr", "next-train-ct-android-release-key") // adapted from source: https://pilloxa.gitlab.io/posts/safer-passwords-in-gradle/




android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
Expand All @@ -96,6 +119,16 @@ android {
abiFilters "armeabi-v7a", "x86"
}
}
signingConfigs {
release {
if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword keychain_password
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword keychain_password
}
}
}
splits {
abi {
reset()
Expand All @@ -108,6 +141,7 @@ android {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
signingConfig signingConfigs.release
}
}
// applicationVariants are e.g. debug, release
Expand Down
2 changes: 2 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class MainApplication extends Application implements ReactApplication {

private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected boolean getUseDeveloperSupport() {
public boolean getUseDeveloperSupport() { // changed "protected" to "public" per https://github.com/airbnb/lottie-react-native/issues/38
return BuildConfig.DEBUG;
}

Expand Down
Binary file modified android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@
# org.gradle.parallel=true

android.useDeprecatedNdk=true

#
# For releasing to the android store:
#

MYAPP_RELEASE_STORE_FILE=next-train-ct-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=next-train-ct-key-alias
# MYAPP_RELEASE_STORE_PASSWORD=***** # avoid using these by adding them to the Mac OS X Keychain
# MYAPP_RELEASE_KEY_PASSWORD=***** # avoid using these by adding them to the Mac OS X Keychain
149 changes: 149 additions & 0 deletions components/About.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React, {Component} from 'react';
import {Text, StyleSheet, Linking, TouchableOpacity} from 'react-native'
import {
Container, Header, Title, Content, Footer, Button, Icon, List, ListItem, Badge
} from 'native-base';

export default class About extends Component {
constructor(props){
super(props)
this.navigator = this.props.navigator;
this.goBack = this.goBack.bind(this);
this.goEmail = this.goEmail.bind(this);
this.goTweet = this.goTweet.bind(this);
this.goVenmo = this.goVenmo.bind(this);
this.goClientRepo = this.goClientRepo.bind(this);
this.goServerRepo = this.goServerRepo.bind(this);
this.goGTFS = this.goGTFS.bind(this);
}

render() {
return (
<Container>
<Header>
<Button transparent onPress={this.goBack}><Icon name="md-arrow-back" /></Button>
<Title>About NextTrain CT</Title>
</Header>

<Content style={styles.content}>

<Title style={styles.firstHeading}>About</Title>
<Text style={styles.text}>
Search Shore Line East transit schedules in a fast and easy way.
Save a route to your favorites, then find out when the next train is coming anytime with the click of a button.
</Text>

<Title style={styles.heading}>Disclaimer</Title>
<Text style={styles.text}>
This app relies on Shore Line East schedule data published online by CTtransit.
The app developer is not affiliated with Shore Line East or CTtransit, and cannot guarantee the accuracy of their data.
If you notice an issue with the schedule data, please contact the developer, who will notify CTtransit.
</Text>

<Title style={styles.heading}>Contact</Title>
<Text style={styles.text}>
This app was made by MJ Rossetti, a native of Branford, CT.
It relies on a backend web server which is operated by his software development company, Data Creative, as a free public service.
</Text>
<List>
<ListItem iconLeft style={styles.listItem}>
<Button transparent onPress={this.goTweet}>
<Icon name='logo-twitter' style={styles.listItemIcon}/>
<Text style={styles.listItemText}>Tweet @data_creative</Text>
</Button>
</ListItem>
<ListItem iconLeft style={styles.listItem}>
<Button transparent onPress={this.goEmail}>
<Icon name='md-mail' style={styles.listItemIcon}/>
<Text style={styles.listItemText}>Email datacreativellc@gmail.com</Text>
</Button>
</ListItem>
</List>

<Title style={styles.heading}>Donate</Title>
<Text style={styles.text}>
If you like this app, please consider donating.
Your donations keep the servers running!
</Text>
<List>
<ListItem iconLeft style={styles.listItem}>
<Button transparent onPress={this.goVenmo}>
<Icon name='logo-usd' style={styles.listItemIcon}/>
<Text style={styles.listItemText}>{"Venmo $2.99 to @data_creative"}</Text>
</Button>
</ListItem>
</List>

<Title style={styles.heading}>Contribute</Title>
<Text style={styles.text}>
This app is powered by open data and open source software.
If you know how to submit a PR or file an issue on GitHub, your contributions are welcome!
</Text>
<List>
<ListItem iconLeft style={styles.listItem}>
<Button transparent onPress={this.goClientRepo}>
<Icon name='logo-github' style={styles.listItemIcon}/>
<Text style={styles.listItemText}>Contribute to the mobile app</Text>
</Button>
</ListItem>
<ListItem iconLeft style={styles.listItem}>
<Button transparent onPress={this.goServerRepo}>
<Icon name='logo-github' style={styles.listItemIcon}/>
<Text style={styles.listItemText}>Contribute to the web service</Text>
</Button>
</ListItem>
</List>


</Content>

</Container>
);
}

goBack(){
this.navigator.pop()
}

linkTo(url){
Linking.openURL(url).catch(function(err){ console.error("LINKING ERROR", err) })
}

goEmail(){
this.linkTo("mailto:datacreativellc@gmail.com")
}

goTweet(){
this.linkTo("https://twitter.com/data_creative")
}

goVenmo(){
this.linkTo("https://venmo.com/data_creative?txn=pay&amount=2.99")
}

goClientRepo(){
this.linkTo("https://github.com/data-creative/NextTrainCT")
}

goServerRepo(){
this.linkTo("https://github.com/data-creative/next-train-api")
}

goGTFS(){
this.linkTo("https://www.cttransit.com/about/developers")
}
};

About.propTypes = {
navigator: React.PropTypes.object.isRequired // an instance of react-native Navigator
};

const styles = StyleSheet.create({
content: {margin: 20},
text: {marginTop: 10},
firstHeading: {color: "#7a7a7a"},
heading: {color: "#7a7a7a", marginTop: 10},
listItem: {height:60},
listItemIcon: {marginRight:13, color:'#282828'},
listItemText: {fontSize:18}
});
14 changes: 11 additions & 3 deletions components/favs/Index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {Component} from 'react';
import {Alert, Text, StyleSheet, AsyncStorage} from 'react-native'
import {Text, StyleSheet, AsyncStorage} from 'react-native'
import {Container, Header, Title, Content, Footer, Button, Icon, Card, CardItem, Spinner} from 'native-base';

import SwipeList from "./SwipeList"
Expand All @@ -9,7 +9,7 @@ export default class FavsIndex extends Component {

constructor(props){
super(props)
console.log("INDEX PROPS FAVS", typeof(this.props.favs), this.props.favs)
//console.log("INDEX PROPS FAVS", typeof(this.props.favs), this.props.favs)
this.state = {
favs: (this.props.favs ? this.props.favs : []),
displaySpinner:false
Expand All @@ -18,6 +18,7 @@ export default class FavsIndex extends Component {
this.fetchAll = this.fetchAll.bind(this);
this.removeAll = this.removeAll.bind(this);
this.handleButtonPress = this.handleButtonPress.bind(this);
this.goMenu = this.goMenu.bind(this);
}

render() {
Expand All @@ -28,6 +29,9 @@ export default class FavsIndex extends Component {
return (
<Container>
<Header>
<Button transparent onPress={this.goMenu}>
<Icon name="md-menu" />
</Button>
<Title>NextTrain CT</Title>
</Header>

Expand All @@ -46,10 +50,14 @@ export default class FavsIndex extends Component {
}

handleButtonPress(){
const favs = this.state.favs // todo
const favs = this.state.favs
this.navigator.push({name: 'NEW_FAV', params:{favs:favs}})
}

goMenu(){
this.navigator.push({name: 'ABOUT'})
}

fetchAll(){
AsyncStorage.getItem('favs')
.then(function(results){ console.log("FETCH ALL", results);
Expand Down
Loading

0 comments on commit 1fb3201

Please sign in to comment.