The earlier variants of the RecyclerView were the ListView and the GridView. Luckily they are a little simpler (less abstract): http://www.grokkingandroid.com/first-glance-androids-recyclerview/.
The ListView is used to display a linear list of items while the GridView is used to display a grid (matrix) of them: http://developer.android.com/guide/topics/ui/layout/gridview.html.
An example of this implemented in our test application is: https://github.com/larkintuckerllc/HelloAndroid/commit/3e8d4b50879fc0102ee67728cb77abb4c30c3b9a
From Android and Back Again
Blog (read from bottom up) on picking back up Android development.
Wednesday, September 2, 2015
Saturday, August 22, 2015
Content Providers
Looking at the Android API Guide, content providers appears to be the remaining core components that I have not touched upon: http://developer.android.com/guide/topics/providers/content-providers.html.
Turns out, however, that is a specialty case that we are unlikely to use very often.
Turns out, however, that is a specialty case that we are unlikely to use very often.
You don't need to develop your own provider if you don't intend to share your data with other applications.So, will pass on them for now.
Friday, August 21, 2015
Services and Broadcast Receivers
The Google Cloud Messaging (GCM) implementation was full of services, intents, etc., and while I understood the gist, I felt that it was important to rewind a bit and understand services and working with them a bit more.
The first thing I recall from the GCM implementation is the intent-filter and implicit intents on the services and receivers. So, we learn about these in the context of activities at: http://developer.android.com/training/basics/intents/index.html. The key phrase is:
When your app is installed on a device, the system identifies your intent filters and adds the information to an internal catalog of intents supported by all installed apps.
Now we move onto services: http://developer.android.com/guide/components/services.html.
note: The discussion about services running in the main thread got me sidetracked; thinking about how Android works under the hood; essentially with a message loop: http://codetheory.in/android-handlers-runnables-loopers-messagequeue-handlerthread/.
Looks like a common pattern is to simply use the IntentService class as it will allow for background processing; separate thread and stopping itself, e.g., a file download etc.
Also, one important thing to note is that services are singletons, i.e., subsequent calls to startService only trigger the onStartCommand method (not the onCreate).
In our example, we will create an IntentService that will run with a delay of five seconds and throw up a status bar notification.
Example changes: https://github.com/larkintuckerllc/HelloAndroid/commit/3b1c5418be7762178f2816552de8afad1ebd3325
Looking back at the GCM example code then: then: https://github.com/larkintuckerllc/HelloAndroid/commit/3c023cca2c460a81c8f580174c13276b58e10067
we have three exported = false services (i.e., can only be started by components in the application).
RegistrationIntentService
This service has no intent-filters and thus must be started explicitly, i.e., initiated by a call including the class itself. It extends IntentService; so it runs in its own thread and exits on its own. It is called from MainActivity's onCreate method (essentially when the application is launched).
Looking at the functionality, it is designed to register the application with GCM (listening to certain topics) and then to pass an identifying token to a backend server (say to allow it to message it individually).
MyGcmListenerService
This service registers itself with the action of (but is can only be initiated by the application itself) com.google.android.c2dm.intent.RECEIVE. It extends GcmListenerService (unclear if it is IntentService under the hood).
Looking at the functionality, it is designed to take action when the GCM message comes in.
note: At first, it was not clear as to why this service had to be started with an implicit intent (likewise with the MyInstanceIDListenerService service). But, my general understanding is that it is Google's code that needs to trigger these services and it is not aware of the actual classes (in JavaScript, one would have simply passed call-back functions to Google's code).
MyInstanceIDListenerService
This service registers itself with the action of (but is can only be initiated by the application itself)
com.google.android.gms.iid.InstanceID. It extends InstanceIDListenerService (unclear if it is IntentService under the hood).
Looking at the functionality, it is designed to take action (calling the RegistrationIntentService) when something happens to the service that requires it to re-register.
Broadcast Receivers
Just when I was getting a handle on services, it still want clear how the GCM messages received outside the application were triggering the internal only services, e.g., MyGcmListenerService (remember these were not exported).
But, there is another bit of the example that I did not understand, i.e., the receiver in the AndroidManifest.xml and the BroadcastReceiver code in MainActivity's onCreate method.
The answer lies in broadcast receivers: http://developer.android.com/guide/topics/manifest/receiver-element.html. What makes them a little confusing is that they are somewhat similar to services in that they can respond to intents (e.g, seems very similar to our IntentService). Seems like the primary difference is that implicit intents and services will only be sent to a single service (OS will limit it to the one that is picked by the user) and intents and broadcast receivers can go to multiple.
com.google.android.gms.gcm.GcmReceiver
This broadcast receiver is registered in AndroidManifest.xml and implemented in Google's code. It is exported with an intent filter looking for:
action: com.google.android.c2dm.intent.RECEIVE
category: com.larkintuckerllc.helloandroid
It also requires the sender to have the permission: com.google.android.c2dm.permission.SEND
This is the missing link that is catching the GCM messages and dispatching them to the locally defined services: MyGcmListenerService and MyInstanceIDListenerService.
mRegistrationBroadcastReceiver
One more broadcast receiver is setup; this one only local (not exported) and is created in the MainActivity's onCreate method (one of those funky anonymous classes) and registered (and deregistered) in the activity's onResume (and onPause) methods.
This is how the MainActivity can be informed when the RegistrationIntentService completes (the broadcast is send at the end).
Security
First one needs to understand permissions: http://developer.android.com/guide/topics/security/permissions.html.
In the GCM example the broadcast receiver registered in the AndroidManifest.xml requires the permission com.google.android.c2dm.permission.SEND on the sender before receiving. Seems that an application can simply set itself to use this permission to fake send GCM messages (but guess the user would have to allow this to install).
The only other thing that is non-obvious is how does the GCM solution prevent other applications from snooping on the GCM messages (they are broadcast). Guessing the answer is that the messages are encrypted and unlocked by GcmReceiver using the secret data in the google-services.json file.
Whew!
we have three exported = false services (i.e., can only be started by components in the application).
RegistrationIntentService
This service has no intent-filters and thus must be started explicitly, i.e., initiated by a call including the class itself. It extends IntentService; so it runs in its own thread and exits on its own. It is called from MainActivity's onCreate method (essentially when the application is launched).
Looking at the functionality, it is designed to register the application with GCM (listening to certain topics) and then to pass an identifying token to a backend server (say to allow it to message it individually).
MyGcmListenerService
This service registers itself with the action of (but is can only be initiated by the application itself) com.google.android.c2dm.intent.RECEIVE. It extends GcmListenerService (unclear if it is IntentService under the hood).
Looking at the functionality, it is designed to take action when the GCM message comes in.
note: At first, it was not clear as to why this service had to be started with an implicit intent (likewise with the MyInstanceIDListenerService service). But, my general understanding is that it is Google's code that needs to trigger these services and it is not aware of the actual classes (in JavaScript, one would have simply passed call-back functions to Google's code).
MyInstanceIDListenerService
This service registers itself with the action of (but is can only be initiated by the application itself)
com.google.android.gms.iid.InstanceID. It extends InstanceIDListenerService (unclear if it is IntentService under the hood).
Looking at the functionality, it is designed to take action (calling the RegistrationIntentService) when something happens to the service that requires it to re-register.
Broadcast Receivers
Just when I was getting a handle on services, it still want clear how the GCM messages received outside the application were triggering the internal only services, e.g., MyGcmListenerService (remember these were not exported).
But, there is another bit of the example that I did not understand, i.e., the receiver in the AndroidManifest.xml and the BroadcastReceiver code in MainActivity's onCreate method.
The answer lies in broadcast receivers: http://developer.android.com/guide/topics/manifest/receiver-element.html. What makes them a little confusing is that they are somewhat similar to services in that they can respond to intents (e.g, seems very similar to our IntentService). Seems like the primary difference is that implicit intents and services will only be sent to a single service (OS will limit it to the one that is picked by the user) and intents and broadcast receivers can go to multiple.
com.google.android.gms.gcm.GcmReceiver
This broadcast receiver is registered in AndroidManifest.xml and implemented in Google's code. It is exported with an intent filter looking for:
action: com.google.android.c2dm.intent.RECEIVE
category: com.larkintuckerllc.helloandroid
It also requires the sender to have the permission: com.google.android.c2dm.permission.SEND
This is the missing link that is catching the GCM messages and dispatching them to the locally defined services: MyGcmListenerService and MyInstanceIDListenerService.
mRegistrationBroadcastReceiver
One more broadcast receiver is setup; this one only local (not exported) and is created in the MainActivity's onCreate method (one of those funky anonymous classes) and registered (and deregistered) in the activity's onResume (and onPause) methods.
This is how the MainActivity can be informed when the RegistrationIntentService completes (the broadcast is send at the end).
Security
First one needs to understand permissions: http://developer.android.com/guide/topics/security/permissions.html.
In the GCM example the broadcast receiver registered in the AndroidManifest.xml requires the permission com.google.android.c2dm.permission.SEND on the sender before receiving. Seems that an application can simply set itself to use this permission to fake send GCM messages (but guess the user would have to allow this to install).
The only other thing that is non-obvious is how does the GCM solution prevent other applications from snooping on the GCM messages (they are broadcast). Guessing the answer is that the messages are encrypted and unlocked by GcmReceiver using the secret data in the google-services.json file.
Whew!
Wednesday, August 19, 2015
Tools and SDK Versions
Ok, even after reading scores of documents on this topic, the whole business of tools and SDK versions is still confusing. My best understanding is:
note: As before, upgrading to build tools 23.0.0 caused weird errors; staying with 22.0.1.
As for SDK Platform and the appropriate version, there seems to be a lot of discussion on this.
By default, Android Studio will typically use the latest installed SDK Platform. The argument for this approach is then to allow one to conditionally use the latest features at run-time (checking for version). In this project, I used the latest version of several libraries, e.g., appcompat, etc, so have to continue to stay with the latest version of SDK Platform.
The opposite argument is to use the lowest possible SDK Platform (above the minSdkVersion) that will compile. This is to catch any incompatibles with hard errors (rather than warnings based on minSdkVersion).
- Android SDK Tools: Platform independent; use latest version.
- Android SDK Build-Tools: Platform independent; use latest version.
- Android SDK Platform-Tools: Platform dependent; backward compatible; use latest version.
- SDK Platform: Platform dependent; use appropriate version.
note: As before, upgrading to build tools 23.0.0 caused weird errors; staying with 22.0.1.
As for SDK Platform and the appropriate version, there seems to be a lot of discussion on this.
By default, Android Studio will typically use the latest installed SDK Platform. The argument for this approach is then to allow one to conditionally use the latest features at run-time (checking for version). In this project, I used the latest version of several libraries, e.g., appcompat, etc, so have to continue to stay with the latest version of SDK Platform.
The opposite argument is to use the lowest possible SDK Platform (above the minSdkVersion) that will compile. This is to catch any incompatibles with hard errors (rather than warnings based on minSdkVersion).
Google Cloud Messaging
In the project that I am working on I had to figure out if I changed the applicationId, would an existing Google Cloud Messaging configuration continue to work.
So, I followed the instructions from Google: https://developers.google.com/cloud-messaging/android/client and set up GCM using the com.larkintuckerllc.helloandroid package name.
It is during the step get configuration file, we provide the package name. At this point, is not clear if it is the applicationId or the actual package name that is relevant; I used the package name (same as applicationId in one of the applications). The mysterious file created is:
google-services.json
Apparently it is the gradle plugin that parses this file and pushes these configuration values into your application.
Going through the documentation, I realized that it got fairly sparse on what appears to be a seemingly overly complicated endeavor. So, I ended having to download their sample application and copy large chunks of code over to get it working: https://developers.google.com/cloud-messaging/android/start
Purely by accident (left off the plugin step), I figured out the connection between the google-services.json file and my application came in through some generated resources, e.g., R.string.gcm_defaultSenderId. Turns out that the value of this string is the project_number in the json file.
So, my interpretation of what is going on is that all this gobbly-gook with json files and gradle is to have the project_number (identifies clients that are allowed to listen) be automatically be injected as a string resource in the project (thus needing the package name - not applicationId).
The one relatively simple thing was to figure out how to send messages to the application: use a tool, e.g., Chrome application Postman to send HTTP post: https://developers.google.com/cloud-messaging/topic-messaging
Was able to reverse engineer the example code to determine the appropriate to: /topics/global
Not sure that I ended up liking the code that I ended up pasting in, e.g,. seemed unnecessarily complicated, e.g., used anonymous classes at one point and there seem to be some extraneous classes. At some point, I would likely choose to refactor it to my liking. But are the changes anyway: https://github.com/larkintuckerllc/HelloAndroid/commit/3c023cca2c460a81c8f580174c13276b58e10067
So, I followed the instructions from Google: https://developers.google.com/cloud-messaging/android/client and set up GCM using the com.larkintuckerllc.helloandroid package name.
It is during the step get configuration file, we provide the package name. At this point, is not clear if it is the applicationId or the actual package name that is relevant; I used the package name (same as applicationId in one of the applications). The mysterious file created is:
google-services.json
{
"project_info": {
"project_id": "fromandbackagain",
"project_number": "1009647335975",
"name":"FromAndBackAgain"},
"client":[
{
"client_info": {
"client_id": "android:com.larkintuckerllc.helloandroid",
"client_type":1,
"android_client_info": {
"package_name":"com.larkintuckerllc.helloandroid"
}
},
"oauth_client":[],
"services": {
"analytics_service":{
"status":1
},
"cloud_messaging_service": {
"status":2,"apns_config":[]
},
"appinvite_service":
{
"status":1,"other_platform_oauth_client":[]
},
"google_signin_service":{
"status":1
},
"ads_service":{
"status":1
}
}
}
],
"ARTIFACT_VERSION":"1"
}
Apparently it is the gradle plugin that parses this file and pushes these configuration values into your application.
Going through the documentation, I realized that it got fairly sparse on what appears to be a seemingly overly complicated endeavor. So, I ended having to download their sample application and copy large chunks of code over to get it working: https://developers.google.com/cloud-messaging/android/start
Purely by accident (left off the plugin step), I figured out the connection between the google-services.json file and my application came in through some generated resources, e.g., R.string.gcm_defaultSenderId. Turns out that the value of this string is the project_number in the json file.
So, my interpretation of what is going on is that all this gobbly-gook with json files and gradle is to have the project_number (identifies clients that are allowed to listen) be automatically be injected as a string resource in the project (thus needing the package name - not applicationId).
The one relatively simple thing was to figure out how to send messages to the application: use a tool, e.g., Chrome application Postman to send HTTP post: https://developers.google.com/cloud-messaging/topic-messaging
Was able to reverse engineer the example code to determine the appropriate to: /topics/global
Not sure that I ended up liking the code that I ended up pasting in, e.g,. seemed unnecessarily complicated, e.g., used anonymous classes at one point and there seem to be some extraneous classes. At some point, I would likely choose to refactor it to my liking. But are the changes anyway: https://github.com/larkintuckerllc/HelloAndroid/commit/3c023cca2c460a81c8f580174c13276b58e10067
Tuesday, August 18, 2015
Creating a Tablet Only Version
While it is against normal practice, I explored creating a tablet only version of this application and strangely ran into all sorts of issues.
My first attempt was to set the minSdkVersion to 15 (greater than 13) and then use android:requiresSmallestWidthDp="600" to target only large enough screens. This did not work as expected (a normal size phone seemed to pick up the option to install).
The following article appears to address this: http://developer.android.com/guide/practices/screens-distribution.html
So, I followed their instructions and hope that it is right (would have to find a device that was large screen but was smaller than 600dp on the smaller dimension to see).
Another annoying this is that I was fighting the Designed for phones label in the Play Store. Tried things like adding a layout for xlarge and ensuring that android:largeScreens="true" and android:xlargeScreens="true" were set. After much Googling, it appears that one need to provide screenshots with lots of space being used too.
The result: https://github.com/larkintuckerllc/HelloAndroid/commit/8fbb8c75f34b6848048a251b02ff8928588c68f8
My first attempt was to set the minSdkVersion to 15 (greater than 13) and then use android:requiresSmallestWidthDp="600" to target only large enough screens. This did not work as expected (a normal size phone seemed to pick up the option to install).
The following article appears to address this: http://developer.android.com/guide/practices/screens-distribution.html
So, I followed their instructions and hope that it is right (would have to find a device that was large screen but was smaller than 600dp on the smaller dimension to see).
Another annoying this is that I was fighting the Designed for phones label in the Play Store. Tried things like adding a layout for xlarge and ensuring that android:largeScreens="true" and android:xlargeScreens="true" were set. After much Googling, it appears that one need to provide screenshots with lots of space being used too.
The result: https://github.com/larkintuckerllc/HelloAndroid/commit/8fbb8c75f34b6848048a251b02ff8928588c68f8
Friday, August 14, 2015
Creating a Library Module
Another way to bring in functionality to our Android application is via a library module: https://developer.android.com/tools/projects/index.html.
A good walk-through: http://hessan.annahid.com/game-development-days/day-9/
An example with changes: https://github.com/larkintuckerllc/HelloAndroid/commit/8a89f92132c759fa5b75f0b0b2144d8c97b4eec7
A good walk-through: http://hessan.annahid.com/game-development-days/day-9/
An example with changes: https://github.com/larkintuckerllc/HelloAndroid/commit/8a89f92132c759fa5b75f0b0b2144d8c97b4eec7
Maven and jcenter
Ok... As I continue to scratch my way through the work project I uncover more stuff to learn. The first is Maven: https://maven.apache.org/. This software is well embedded in Android Studio (or embedded in Gradle that is embedded). Either way, it appears to be package management software much like Bower or NPM in the world of web development.
The next piece of the puzzle is jcenter: https://bintray.com/bintray/jcenter. In a nutshell, this is a repository of Maven packages that one can include in their project: http://inthecheesefactory.com/blog/how-to-upload-library-to-jcenter-maven-central-as-dependency/en.
The following are the changes that one makes to use an example package (e.g., joda-time:joda-time):
Changes: https://github.com/larkintuckerllc/HelloAndroid/commit/92e74b50e18908f3e24640e80a014017a410f9b6
The next piece of the puzzle is jcenter: https://bintray.com/bintray/jcenter. In a nutshell, this is a repository of Maven packages that one can include in their project: http://inthecheesefactory.com/blog/how-to-upload-library-to-jcenter-maven-central-as-dependency/en.
The following are the changes that one makes to use an example package (e.g., joda-time:joda-time):
Changes: https://github.com/larkintuckerllc/HelloAndroid/commit/92e74b50e18908f3e24640e80a014017a410f9b6
Integrating Fabric
First, what is Fabric: http://www.wired.com/2014/10/twitter-fabric-sdk/.
Two, how does one install and use it: http://docs.fabric.io/. Some tips on getting going with Fabric.
Two, how does one install and use it: http://docs.fabric.io/. Some tips on getting going with Fabric.
- Had a hard time signing up for an account with Fabric; maybe you will not experience this.
Ok, bottom line it was easy to setup. Even tested by creating a crash and saw the results in the monitoring page.
Changes: https://github.com/larkintuckerllc/HelloAndroid/commit/1aafae2b43cd378388d9dd5c4806041f13954a53
Supporting Multiple Screen Sizes
It is generally good practice to create a single APK that supports multiple screen sizes using alternative layouts: http://developer.android.com/guide/practices/screens_support.html. At the same time, one can also target different flavors to different screen sizes and accomplish the same thing.
In this example, I am looking to lock the screen in landscape orientation for all activities on xlarge screens and in portrait orientation for all activities on small, normal, and large screens. As it requires changes to the AndroidManifest.xml document for these changes; this forces us to use the multiple flavors approach.
In my evolving example, I go ahead and create additional flavors:
AndroidManifest.xml (notelphony)
Using the aapt tool we see that it now reports:
Now that we getting deeper into merging manifests we need to read up on them more: https://developer.android.com/tools/building/manifest-merge.html.
While the document does refer to a document that can be used to troubleshoot the merge: manifest-merger-<productFlavor>-report.txt, it did not seem to contain the details that I was expecting. Rather I dug around and found what appears to the the final composite AndroidManifest.xml file under ./app/build/intermediates.
note: While I never found documentation that explains exactly how the merging works (just lots of examples), it is clear that andoid:name has special meaning and the merge process will match blocks up based on it.
So, we add to the notelephony flavor AndroidManifest.xml file the uses-feature for portrait and screenOrientation to each of the activities.
AndroidManifest.xml
Now this flavor always shows two activites vertically and the aapt tool reports:
In this example, I am looking to lock the screen in landscape orientation for all activities on xlarge screens and in portrait orientation for all activities on small, normal, and large screens. As it requires changes to the AndroidManifest.xml document for these changes; this forces us to use the multiple flavors approach.
In my evolving example, I go ahead and create additional flavors:
- notelephony
- notelephonytablet (new)
- telephony
- telephonytablet (new)
AndroidManifest.xml (notelphony)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.larkintuckerllc.helloandroid" >
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="false"
/>
</manifest>
Using the aapt tool we see that it now reports:
supports-screens: 'small' 'normal' 'large'This only targets the APK to the proper devices, now to actually force an orientation for the activities:
Now that we getting deeper into merging manifests we need to read up on them more: https://developer.android.com/tools/building/manifest-merge.html.
While the document does refer to a document that can be used to troubleshoot the merge: manifest-merger-<productFlavor>-report.txt, it did not seem to contain the details that I was expecting. Rather I dug around and found what appears to the the final composite AndroidManifest.xml file under ./app/build/intermediates.
note: While I never found documentation that explains exactly how the merging works (just lots of examples), it is clear that andoid:name has special meaning and the merge process will match blocks up based on it.
So, we add to the notelephony flavor AndroidManifest.xml file the uses-feature for portrait and screenOrientation to each of the activities.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.larkintuckerllc.helloandroid" >
<uses-feature android:name="android.hardware.screen.portrait" />
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="false"
/>
<application>
<activity
android:name=".MainActivity"
android:screenOrientation="portrait">
</activity>
<activity
android:name=".ThingActivity"
android:screenOrientation="portrait">
</activity>
</application>
</manifest
Now this flavor always shows two activites vertically and the aapt tool reports:
uses-feature: name='android.hardware.screen.portrait'With all this in place, here are the changes to the code to support this configuration: https://github.com/larkintuckerllc/HelloAndroid/commit/09e643979d3e15980382fa31b22dbfdc3cb67cfc
Tuesday, August 11, 2015
Multiple APK Support
Now that we have a better understanding of the build tools, we can dive into the topic of multiple APK support: https://developer.android.com/google/play/publishing/multiple-apks.html. As a demonstration, will be creating two different flavors of the app: one that supports phone calls and one that does not.
note: Not convinced that we have to actually create two flavors, i.e., if the phone feature was used in a limited fashion then seems like one could set the feature to be optional and handle the existence of the feature in the code itself. For now, however, will plow on by creating two flavors.
For phone calls, the following are important entries in the AndroidManifest.xml. How these are relevant is discussed in: http://developer.android.com/guide/topics/manifest/uses-feature-element.html.
MainActivity.java
While the application compiles and even runs, clicking the Call menu item causes a fatal exception (security exception). The simple fix is to add the following to the AndroidManifest.xml file.
AndroidManifest.xml
The issue now is that our application now has a feature requirement of telephony and will not be available to devices without this feature (tablets).
To address this, we will refactor our code to first check for telephony feature ability in the code before trying to make the call.
MainActivity.java
Then we can change up AndroidManifest.xml to allow the app to be loaded on devices without phones (had to add the READ_PHONE_STATE permission because of the phone check).
AndroidManifest.xml
With this in place, we now have the feature requirements as follows:
Following what we have learned can make two flavors with different requirements:
telephony
We use a single applicationId but two different versionCode entries to distinguish the two flavors. The version code was selected so that the phone code always have a higher version number (so that devices that meet all the feature sets, i.e., phones, will get the phone code).
With all this done, we go through the publishing process and create two APK files (app-notelephony-release.apk and app-telephony-release.apk): https://developer.android.com/tools/publishing/publishing_overview.html.
The result of all this work can be see in the changes: https://github.com/larkintuckerllc/HelloAndroid/commit/128adfe13bad0f20e794a467e8810e28b9411299.
note: Not convinced that we have to actually create two flavors, i.e., if the phone feature was used in a limited fashion then seems like one could set the feature to be optional and handle the existence of the feature in the code itself. For now, however, will plow on by creating two flavors.
For phone calls, the following are important entries in the AndroidManifest.xml. How these are relevant is discussed in: http://developer.android.com/guide/topics/manifest/uses-feature-element.html.
- permission: android.permission.CALL_PHONE
- feature: android.hardware.telephony
uses-feature: name='android.hardware.bluetooth'Ok, for our application we startup with a minimal set of feature requirements:
uses-implied-feature: name='android.hardware.bluetooth' reason='requested android.permission.BLUETOOTH permission, requested android.permission.BLUETOOTH_ADMIN permission, and targetSdkVersion > 4'
uses-feature: name='android.hardware.location'
uses-implied-feature: name='android.hardware.location' reason='requested android.permission.ACCESS_COARSE_LOCATION permission, and requested android.permission.ACCESS_FINE_LOCATION permission'
uses-feature: name='android.hardware.location.gps'
uses-implied-feature: name='android.hardware.location.gps' reason='requested android.permission.ACCESS_FINE_LOCATION permission'
uses-feature: name='android.hardware.location.network'
uses-implied-feature: name='android.hardware.location.network' reason='requested android.permission.ACCESS_COARSE_LOCATION permission'
uses-feature: name='android.hardware.screen.portrait'
uses-implied-feature: name='android.hardware.screen.portrait' reason='one or more activities have specified a portrait orientation'
uses-feature: name='android.hardware.telephony'
uses-implied-feature: name='android.hardware.telephony' reason='requested a telephony permission'
uses-feature: name='android.hardware.touchscreen'
uses-implied-feature: name='android.hardware.touchscreen' reason='default feature for all apps'
uses-feature: name='android.hardware.wifi'
uses-implied-feature: name='android.hardware.wifi' reason='requested android.permission.ACCESS_WIFI_STATE permission, and requested android.permission.CHANGE_WIFI_STATE permission'
uses-feature: name='android.hardware.touchscreen'uses-implied-feature: name='android.hardware.touchscreen' reason='default feature for all apps'We first try to have the app make a phone call (say on a menu click) with the following:
MainActivity.java
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_hello) {
Snackbar
.make(findViewById(R.id.snackbarPosition), R.string.snackbar_text, Snackbar.LENGTH_LONG)
.show();
return true;
}
if (id == R.id.action_call) {
Intent phoneCallIntent = new Intent(Intent.ACTION_CALL);
phoneCallIntent.setData(Uri.parse("tel:3524748328"));
startActivity(phoneCallIntent);
}
return super.onOptionsItemSelected(item);
}
While the application compiles and even runs, clicking the Call menu item causes a fatal exception (security exception). The simple fix is to add the following to the AndroidManifest.xml file.
AndroidManifest.xml
<uses-permission android:name="android.permission.CALL_PHONE" />
The issue now is that our application now has a feature requirement of telephony and will not be available to devices without this feature (tablets).
uses-feature: name='android.hardware.telephony'uses-implied-feature: name='android.hardware.telephony' reason='requested a telephony permission'uses-feature: name='android.hardware.touchscreen'uses-implied-feature: name='android.hardware.touchscreen' reason='default feature for all apps'
To address this, we will refactor our code to first check for telephony feature ability in the code before trying to make the call.
MainActivity.java
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_hello:
Snackbar
.make(findViewById(R.id.snackbarPosition), R.string.snackbar_text, Snackbar.LENGTH_LONG)
.show();
return true;
case R.id.action_call:
if (((TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number()
== null) {
Snackbar
.make(findViewById(R.id.snackbarPosition), R.string.no_telephony, Snackbar.LENGTH_LONG)
.show();
return true;
}
Intent phoneCallIntent = new Intent(Intent.ACTION_CALL);
phoneCallIntent.setData(Uri.parse("tel:3524748328"));
startActivity(phoneCallIntent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Then we can change up AndroidManifest.xml to allow the app to be loaded on devices without phones (had to add the READ_PHONE_STATE permission because of the phone check).
AndroidManifest.xml
<uses-feature android:name="android.hardware.telephony" android:required="false" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
With this in place, we now have the feature requirements as follows:
uses-feature-not-required: name='android.hardware.telephony'The problem, however, now is that the app still has the call menu item and dynamically changing the menu in code adds a bit more complexity (especially if one has to do it repeatedly). This is where we use multiple APK support.
uses-feature: name='android.hardware.touchscreen'
uses-implied-feature: name='android.hardware.touchscreen' reason='default feature for all apps'
Following what we have learned can make two flavors with different requirements:
telephony
uses-feature: name='android.hardware.telephony'uses-feature: name='android.hardware.touchscreen'uses-implied-feature: name='android.hardware.touchscreen' reason='default feature for all apps'notelephony
uses-feature: name='android.hardware.touchscreen'uses-implied-feature: name='android.hardware.touchscreen' reason='default feature for all apps'
We use a single applicationId but two different versionCode entries to distinguish the two flavors. The version code was selected so that the phone code always have a higher version number (so that devices that meet all the feature sets, i.e., phones, will get the phone code).
With all this done, we go through the publishing process and create two APK files (app-notelephony-release.apk and app-telephony-release.apk): https://developer.android.com/tools/publishing/publishing_overview.html.
The result of all this work can be see in the changes: https://github.com/larkintuckerllc/HelloAndroid/commit/128adfe13bad0f20e794a467e8810e28b9411299.
Saturday, August 8, 2015
Sidebar into Build Tools
In the current project one of the problems that I ran into was that the Google Maps Android API was not working. The issue ended up being was that I did not have either the Android debug nor the release certificate that was used to authorize the Google Maps Android API in the application (via the Google Maps Android API Key).
While I knew the quick fix was to simply obtain a new key based on my current debug certificate and insert it into the AndroidManifest.xml file. The problem, however, is that I had no easy way to switch between using keys for debug and release (other than by copying and pasting). But, looking at the result of the Google Maps Android API tutorial I noticed that there is a way to have separate code running when debugging verses when released. The key is using Gradle: https://developer.android.com/tools/building/configuring-gradle.html
Some tips on going through the material:
- At first it was not clear that there is a pulldown just above the list of files that allows one to change views of the folder hierarchy, e.g,. Android vs. Project. In Android view, one only sees the files relevant to the build variant one is working on.
- In working through the example, I missed the step where one copies demo to full. I mistakenly copied the moved files back in to the main folder hierarchy. Interestingly, having duplicate xml files seemed to be fine, but having duplicate classes caused compile errors (has to do with how Gradle handles different types of items).
- While this example walks one through a fairly non-intuitive process of creating flavors, it does not address build types (e.g,. debug vs. release). Based on what learned, one can interpret the Gradle manual on build variants: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants. In particular, creating a folder as app/src/demo to hold any general debug specific items and app/src/demoDebug for any flavor specific ones.
In this particular case the solution was to create two files:
- src/debug/res/values/google_maps_api.xml
- src/main/res/values/google_maps_api.xml
Then we can pull in the string value in AndroidManifest.xml based on whether are running as debug or release.
Thursday, August 6, 2015
Sidebar into Google Maps Android API
While not part of the sample project that I am building, had a need to explore this topic for work.
There not much to say other than it is easy to get going with Google Maps Android API: https://developers.google.com/maps/documentation/android/intro.
A couple of tricks:
There not much to say other than it is easy to get going with Google Maps Android API: https://developers.google.com/maps/documentation/android/intro.
A couple of tricks:
- Google Maps Android API requires Google Play Services which requires API 9 (I was originally setting a minimum of API 7); just need to up the minSdkVersion to 9 in the build.gradle file.
- The preferred way of getting at location data also uses Google Play Services. The example provided by Android: https://developer.android.com/training/location/retrieve-current.html is too sparse. Rather, the following got me there: http://blog.teamtreehouse.com/beginners-guide-location-android
- Learned that as of today (8/6/15) the system images (emulator) for Android 5.1.1 (API 22) ship with an older version of Google Play Services (6774470) but the Android 5.0.1 (API 21) ships with a newer one 7571470. So, to use the emulator with the current shipping client (requires 7571000) one needs to use the Android 5.0.1 (API 21) version.
Monday, August 3, 2015
Fun with Fragments
For our simple application (2 activities), we are going to use fragments to allow a tablet to see both the list of things (to the left) and the selected thing (to the right): http://developer.android.com/training/basics/fragments/index.html.
The first step will be to create a fragment for ThingActivity and move its functionality into it. Using Android Studio's New Fragment > Blank wizard we create ThingFragment.
Will start with ThingFragment as it is fairly easy; only displays the id with no interactivity. The end result consisted of:
note: Because creating the fragment was pretty much from the tutorial (above), opted to simply summarize the actions here. Going forward, thinking I will use GIT more effectively to show differences.
- Creating fragment_thing.xml
- Changing the provided TextView to have id name.
- Creating ThingFragment.java
- Only need to override onCreateView to use the fragment_thing.xml.
- Implemented a custom method selectThing to set the TextView text.
- Updating activity_thing.xml; replacing the TextView with the ThingFragment.
- Updating ThingActivity; replacing setting the TextView text with a call to the custom method selectThing.
Now let us move to MainFragment; this time will share the changes in the commit. Again, we start with the wizard. The gist is as follows:
- Bring layout from activity_main.xml to fragment_main.xml
- Bring functionality from MainActivity to MainFragment
At this point, we leave out the complexity of the clicking for now.
note: Interestingly, when documenting JavaScript frameworks, I have found that I could include the source code changes in my documentation (as I have done thusfar), but Java / Android code is sufficiently heavy that I have had to change to using GIT commits (and their diffs).
Changes: https://github.com/larkintuckerllc/HelloAndroid/commit/fb8f8d330c508cd27188228b08808884e7d780ce
Ok, now we need to be able to take a click in MainFragment and have MainActivity do something with it; doing it as per the documentation. At this point, we have gotten the code back to the original functionality.
Changes: https://github.com/larkintuckerllc/HelloAndroid/commit/b7766e8d201172f97e959b0a4e9b002f8c174104
Now, we need make it so that on a tablet we get a single screen with both fragments showing.
Changes: https://github.com/larkintuckerllc/HelloAndroid/commit/3a15f7efc286e79f05639238b63ec9904f078ab5
Changes: https://github.com/larkintuckerllc/HelloAndroid/commit/b7766e8d201172f97e959b0a4e9b002f8c174104
Now, we need make it so that on a tablet we get a single screen with both fragments showing.
Changes: https://github.com/larkintuckerllc/HelloAndroid/commit/3a15f7efc286e79f05639238b63ec9904f078ab5
Sunday, August 2, 2015
Second Activity and Intents
We can use Android Studio's New Android Activity wizard to create an activity called ThingActivity.
Then let us update onClick method of the ThingViewHolder to start this new activity.
MainActivity.java
Update the activity's XML and onCreate method.
activity_thing.xml
ThingActivity.java
Then there is a bit of work to use the toolbar, e.g., enabling the Toolbar (aka, Action Bar). There is quite a bit here involving updating activity_thing.xml, ThingActivity.java, and AndroidManifest.xml. Also got rid of the code for the menu including: menu_thing.xml. Also All done right, one will have a navigation arrow leading back up to the parent activity. Details on this are at: https://developer.android.com/guide/topics/ui/actionbar.html.
Then let us update onClick method of the ThingViewHolder to start this new activity.
MainActivity.java
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,ThingActivity.class);
intent.putExtra("id", name.getText());
startActivity(intent);
}
Update the activity's XML and onCreate method.
activity_thing.xml
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
ThingActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thing);
String id = getIntent().getStringExtra("id");
((TextView)findViewById(R.id.name)).setText(id);
}
Then there is a bit of work to use the toolbar, e.g., enabling the Toolbar (aka, Action Bar). There is quite a bit here involving updating activity_thing.xml, ThingActivity.java, and AndroidManifest.xml. Also got rid of the code for the menu including: menu_thing.xml. Also All done right, one will have a navigation arrow leading back up to the parent activity. Details on this are at: https://developer.android.com/guide/topics/ui/actionbar.html.
Friday, July 31, 2015
Diving into the RecyclerView
The next major element we are going to introduce is the RecyclerView: https://developer.android.com/training/material/lists-cards.html.
The first thing we need to do is bring in the v7 RecyclerView Library: http://developer.android.com/tools/support-library/features.html by adding one lines to the build.gradle file.
build.gradle
The first step is to create the model that one will use to build the list. While in most applications today this model will come from a RESTFul API, we will simply create the model in the application, e.g., we can create three things as follows:
Add a private list of things to the MainActivity class.
MainActivity.java
and then initialize it in the onCreate method.
MainActivity.java
Now that we have the model, we define the view, first by updating styles.xml:
styles.xml
and creating thing.xml in the res/layout folder:
thing.xml
and updating activity_main.xml; replacing the TextView with the following:
activity_main.xml
With the model and the view in place we need to connect the two (controller type code).
In the OnCreate method of the MainActivity we add the following:
MainActivity.java
The next step is to use the View Holder pattern: http://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder. Create ThingViewHolder in MainActivity.
MainActivity.java
The key then is to create ThingAdapter in MainActivity (seems that I have to stare as this code every time I see it).
MainActivity.java
And finally, we hand an instance of ThingAdapter to the thingRV RecyclerView in the onCreate method of MainActivity.java.
MainActivity.java
Seems like a lot of work to get the three words one, two, and three to show up in a list on the screen.
Now we make the words clickable by updating the ThingViewHolder class as follows:
MainActivity.java
The first thing we need to do is bring in the v7 RecyclerView Library: http://developer.android.com/tools/support-library/features.html by adding one lines to the build.gradle file.
build.gradle
compile 'com.android.support:recyclerview-v7:22.2.0'
The first step is to create the model that one will use to build the list. While in most applications today this model will come from a RESTFul API, we will simply create the model in the application, e.g., we can create three things as follows:
Add a private list of things to the MainActivity class.
MainActivity.java
private List<String> things;
and then initialize it in the onCreate method.
MainActivity.java
things = new ArrayList<String>();
things.add("one");
things.add("two");
things.add("three");
Now that we have the model, we define the view, first by updating styles.xml:
styles.xml
<style name="ListContainer">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:paddingTop">8dp</item>
</style>
<style name="ListItem">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
</style>
<style name="ListContent">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
</style>
<style name="ListSingleLine">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingTop">16dp</item>
<item name="android:paddingRight">16dp</item>
<item name="android:paddingBottom">20dp</item>
<item name="android:textSize">16sp</item>
</style>
<style name="ListDivider">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">1dp</item>
<item name="android:background">?android:attr/listDivider</item>
</style>
and creating thing.xml in the res/layout folder:
thing.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/ListItem">
<RelativeLayout
android:id="@+id/content"
style="@style/ListContent">
<TextView
android:id="@+id/name"
style="@style/ListSingleLine" />
</RelativeLayout>
<View
style="@style/ListDivider"
android:layout_below="@id/content" />
</RelativeLayout>
and updating activity_main.xml; replacing the TextView with the following:
activity_main.xml
<android.support.v7.widget.RecyclerView
android:id="@+id/things"
android:layout_below="@id/toolbar"
style="@style/ListContainer" />
With the model and the view in place we need to connect the two (controller type code).
In the OnCreate method of the MainActivity we add the following:
MainActivity.java
RecyclerView thingRV = (RecyclerView) findViewById(R.id.things); thingRV.setHasFixedSize(true); thingRV.setLayoutManager(new LinearLayoutManager(this));
The next step is to use the View Holder pattern: http://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder. Create ThingViewHolder in MainActivity.
MainActivity.java
public class ThingViewHolder extends RecyclerView.ViewHolder {
String id;
TextView name;
ThingViewHolder(View view) {
super(view);
name = (TextView)view.findViewById(R.id.name);
}
}
The key then is to create ThingAdapter in MainActivity (seems that I have to stare as this code every time I see it).
MainActivity.java
public class ThingAdapter extends RecyclerView.Adapter<ThingViewHolder> {
List<String> things;
ThingAdapter(List<String> things) {
this.things = things;
}
public ThingViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.thing, viewGroup, false);
return new ThingViewHolder(view);
}
public int getItemCount() {
return things.size();
}
public void onBindViewHolder(ThingViewHolder viewHolder, int i) {
viewHolder.id = things.get(i);
viewHolder.name.setText(things.get(i));
}
}
And finally, we hand an instance of ThingAdapter to the thingRV RecyclerView in the onCreate method of MainActivity.java.
MainActivity.java
thingRV.setAdapter(new ThingAdapter(things));
Seems like a lot of work to get the three words one, two, and three to show up in a list on the screen.
Now we make the words clickable by updating the ThingViewHolder class as follows:
MainActivity.java
public class ThingViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
String id;
TextView name;
ThingViewHolder(View view) {
super(view);
name = (TextView)view.findViewById(R.id.name);
view.setOnClickListener(this);
}
public void onClick(View view) {
Snackbar
.make(findViewById(R.id.snackbarPosition), name.getText(), Snackbar.LENGTH_LONG)
.show();
}
}
Saturday, June 13, 2015
Bringing in the Android Design Support Library
In the recent release of the Android Support Library (May 2015), there is now a Design Support Library that implements more of the Material Design elements, e.g., snack bar.
Implementing the Snack Bar
First, we need to bring in the Design Support Library by adding the following to the dependencies in Grade Scripts > build.gradle (Module: app)
Then we need to add the snack bar text to app > res > values > strings.xml:
Then we need to add a CoordinatorLayout (new in the Design Support Library) to app > res > layout > activity_main.xml:
Finally, we implement the snack bar when clicking the "Hello" menu item in the toolbar in the MainActivity class.
Implementing the Snack Bar
First, we need to bring in the Design Support Library by adding the following to the dependencies in Grade Scripts > build.gradle (Module: app)
compile 'com.android.support:design:22.2.0'
Then we need to add the snack bar text to app > res > values > strings.xml:
<string name="snackbar_text">Hello</string>
Then we need to add a CoordinatorLayout (new in the Design Support Library) to app > res > layout > activity_main.xml:
<android.support.design.widget.CoordinatorLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:id="@+id/snackbarPosition"> </android.support.design.widget.CoordinatorLayout>
Finally, we implement the snack bar when clicking the "Hello" menu item in the toolbar in the MainActivity class.
if (id == R.id.action_hello) {
Snackbar
.make(findViewById(R.id.snackbarPosition), R.string.snackbar_text, Snackbar.LENGTH_LONG)
.show();
return true;
}
Friday, June 12, 2015
Building HelloAndroid
Building off the Getting Started training, here is the start of an implementation of HelloAndroid using best practices.
New Project
Using Android Studio (with target SDK version of 22), start a new Android Studio project with:
v7 Support Libraries
Using Android Studio, download the support libraries as documented.
Updated default AppTheme style to use the support library in app > res > values > styles.xml
Then delete app > res > values > styles.xml (v21) get rid of the targeted AppTheme.
Switching from the Action Bar to the Toolbar
The toolbar is the recommended generalization of the Action Bar that is introduced in Android 5.0 / API 21 and is supported by the v7 support libraries.
First, we will update the menu to simply show "hello":
Update app > res > values > strings.xml to the following:
Then we need to update the app > res > menu > menu_main.xml to the following:
Finally, we update the MainActivity class' onOptionsItemSelected method as follows:
Now we are going to get rid of the action bar by editing app > res > values > styles.xml with:
Now we can put in the tool bar by adding the Toolbar style to app > res > values > style.xml
And we update the layout app > res > layout > activity_main.xml
Finally, we need to update the MainActivity class' onCreate method to be:
A small minor detail is to switch out the deprecated ActionBarActivity for the AppCompatActivity of the MainActivity class.
Using the Material Theme
The Material Theme can be customized using three colors: primary, primary dark, and accent (and the original window background) set as follows:
Updated app > res > values > styles.xml with colors:
and updated AppTheme:
New Project
Using Android Studio (with target SDK version of 22), start a new Android Studio project with:
- Phone and Tablet > Minimum SDK: "API 7: Android 2.1 (Eclair)".
- Blank Activity (defaults)
Have to downgrade the build tools (bug in 23.0.0 rc1) by:
- app (right click) > Open Module Settings > Build Tools Version: 22.0.1
Virtual Devices
Using Android Studio > Tools > AVD Manager, create three virtual devices:
- Low Performance Phone
- 2.7" QVGA (small, ldpi)
- Gingerbread (Android 2.3.3 / API 10)
- Chose armeabi (vs. x86); not clear if important.
- High Performance Phone
- Nexus 5 (normal, xxhdpi)
- Lollipop (Android 5.0 / API 22)
- Tablet
- Nexus 7 (large, xhdpi)
- Lollipop (Android 5.0 / API 22)
Using Android Studio, download the support libraries as documented.
Support Library Setup | AndroidAdded appcompat Gradle Scripts > build.gradle (Module: app)
https://developer.android.com/tools/support-library/setup.html
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
}
Updated default AppTheme style to use the support library in app > res > values > styles.xml
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light">
</style>
</resources>
Then delete app > res > values > styles.xml (v21) get rid of the targeted AppTheme.
Switching from the Action Bar to the Toolbar
The toolbar is the recommended generalization of the Action Bar that is introduced in Android 5.0 / API 21 and is supported by the v7 support libraries.
First, we will update the menu to simply show "hello":
Update app > res > values > strings.xml to the following:
<resources>
<string name="app_name">HelloAndroid</string>
<string name="hello_world">Hello world!</string>
<string name="action_hello">Hello</string>
</resources>
Then we need to update the app > res > menu > menu_main.xml to the following:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">
<item android:id="@+id/action_hello" android:title="@string/action_hello"
android:orderInCategory="100" app:showAsAction="ifRoom" />
</menu>
Finally, we update the MainActivity class' onOptionsItemSelected method as follows:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_hello) {
return true;
}
return super.onOptionsItemSelected(item);
}
Now we are going to get rid of the action bar by editing app > res > values > styles.xml with:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
Now we can put in the tool bar by adding the Toolbar style to app > res > values > style.xml
<style name="Toolbar">
<item name="theme">@style/ThemeOverlay.AppCompat.ActionBar</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:minHeight">?attr/actionBarSize</item>
<item name="android:background">?attr/colorPrimary</item>
</style>
And we update the layout app > res > layout > activity_main.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
style="@style/Toolbar" />
<TextView android:text="@string/hello_world"
android:layout_below="@id/toolbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
Finally, we need to update the MainActivity class' onCreate method to be:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
A small minor detail is to switch out the deprecated ActionBarActivity for the AppCompatActivity of the MainActivity class.
Using the Material Theme
The Material Theme can be customized using three colors: primary, primary dark, and accent (and the original window background) set as follows:
Updated app > res > values > styles.xml with colors:
<color name="primary">#AAAAFF</color> <color name="primary_dark">#7777CC</color> <color name="accent">#FF8800</color> <color name="background">#DDDDFF</color>
and updated AppTheme:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
<item name="android:windowBackground">@color/background</item>
</style>
Monday, June 1, 2015
Revisiting the Getting Started Training
In going through the Android Getting Started training, I observed a number of new key elements since Android 1.6 / API 4:
- Action Bar: Introduced in Android 3.0 / API 11, the action bar is a navigation bar at the top of the screen that replaced the navigation functionality provided by the dedicated menu button (no longer required for Android devices).
- Fragments: Also introduced in Android 3.0 / API 11, fragments were introduced to provide a flexible way to combine elements to support varying screen sizes, e.g., phones vs tablets.
Because we want to develop applications that run on virtually all Android devices in use (forces us to support versions Android 2.1 / API 7 or greater), we will be using the v7 Support Libraries that will provide these (and other) newer Android features.
Challenging Documentation (e.g., Action Bar)
One of the challenges in reading this (and other) Android documentation is that there are multiple ways of achieving the end, e.g., with and without the support libraries. While the multiple techniques are fundamentally the same, the devil is in the details and it is easy to get tripped up.
The action bar is an example of a particularly challenging implementation as it requires dealing with both the variation of the use or not of the support libraries and also the introduction of the Toolbar (generalization of the Action Bar) in Android 5.0 / API 21.
The action bar is an example of a particularly challenging implementation as it requires dealing with both the variation of the use or not of the support libraries and also the introduction of the Toolbar (generalization of the Action Bar) in Android 5.0 / API 21.
Sunday, May 31, 2015
Dipping Toes into Material Design
The first thing that I noticed is that the Android folks (Google) are pushing a new design philosophy (infrastructure) called Material Design starting with the Android 5.0 / API 21 release. In order to allow for wide deployment of this design, they have included key elements into the v7 support libraries (allowing for usage in versions greater than or equal to Android 2.1 / API 7).
Incidentally, virtually 100% of Android devices in use are greater than or equal to Android 2.1 / API 7; per Dashboard | Android Developer.
To ease into Material Design for Android, thought to first spin up on AngularJS's implementation of Material Design.
Incidentally, virtually 100% of Android devices in use are greater than or equal to Android 2.1 / API 7; per Dashboard | Android Developer.
To ease into Material Design for Android, thought to first spin up on AngularJS's implementation of Material Design.
Angular Material - IntroductionThe comparable elements that are in the v7 support library are:
https://material.angularjs.org/latest/#/
- Theming
- Toolbar
- Tabs
- Card
- Toast (aka., Snackbar)
Some of the challenges in getting up to speed with Angular Material were:
- It is important to understand AngularJS to start of with.
- Knowing CSS media queries.
- Angular Material is build upon the new Flexbox browser feature; which takes some getting used to.
The following is a basic implementation of Angular Material.
AngularJS: Material Design Demo - 5
http://jsfiddle.net/sckmkny/0far55wa/
Why Bother with Native (Android) Development?
Before I dive into my experience of picking up Android development after a five year pivot to writing web applications, thought to share how I thought this is a good idea.
When the Android operating system started to gain traction in 2009, I decided to take my Java skills (forged in the world of enterprise Java using WebSphere by IBM) to writing mobile applications for Android. Long story short, three key observations later I switched to writing mobile applications using HTML.
When the Android operating system started to gain traction in 2009, I decided to take my Java skills (forged in the world of enterprise Java using WebSphere by IBM) to writing mobile applications for Android. Long story short, three key observations later I switched to writing mobile applications using HTML.
- Building mobile solutions involves delivering three applications: Android, IOS, and Web. Writing three applications, however, is prohibitively expensive for many startup companies.
- Solutions exist to wrap web applications as Android and IOS applications, e.g., Apache Cordova, that expose native functionality (i.e., hybrid applications).
- Web JavaScript MVC frameworks made writing web applications manageable (and familiar to Android application development). While there are many such frameworks, I finally settled on AngularJS due to its widespread adoption and organizational (Google) support.
Can We Please Stop Fighting The Native vs. Web App Wars?http://readwrite.com/2015/02/27/native-vs-web-apps-ceasefireAfter thinking about this, I came up with some observations that justified my recent foray back into Android development. By the way, I did spend a couple of days awhile ago trying to pick up IOS development but it felt distinctly different than either web or Android development; enough so that I decided to stop trying.
- While sufficient (maybe even required) for startup companies, using wrapped web applications for established companies is less appropriate.
- Cost is less of a factor for established companies.
- Difficult (to impossible) to write wrapped web applications that have the same look and polish of native applications; established companies are likely more concerned about brand.
- Web views in native applications can bring in reusable (across all solutions) web content where appropriate; thus achieving some cost reduction benefit of hybrid and web applications.
- While the performance of devices, e.g, computer, phone, and tablet, is making the performance difference between native, hybrid, and web applications less noticeable, new lower performance devices (e.g., watches) are being developed that reintroduces the performance gaps.
Subscribe to:
Posts (Atom)