Showing posts with label Android Studio. Show all posts
Showing posts with label Android Studio. Show all posts

Tuesday, 27 December 2016

Building a NativeScript + Angular 2 Mobile App using Firebase


Jen Looper
Developer Advocate at ProgressSW
Guest blogger Jen Looper
The powerful combination of NativeScript, Firebase, and Angular 2 can kickstart your app building into high gear, especially during the holidays when you find yourself confronted with the need to speed up your app development AND meet your family's gift-giving needs! Just in time, I am happy to presentto you (see what I did there 🎁) a demo of how to leverage Firebase in your Angular 2-powered NativeScript apps using several elements of Eddy Verbruggen's famousNativeScript-Firebase plugin.
In this tutorial, I'm going to show you how to use four popular Firebase elements in your NativeScript app: Authentication with a login and registration routine; Database for data storage and real-time updates; Remote Config to make changes to an app remotely; and Storage for saving photos. To do this, I decided to rewrite my Giftler app, originally written in Ionic.
Before we get started, I encourage you to read through thedocumentation before starting in on your project, and make sure that a few prerequisites are in place:
  • Ensure thatNativeScript is installed on your local machine and that the CLI works as expected
  • Configure your preferred IDE for NativeScript and Angular development. You're going to need TypeScript, so ensure that your transpiling process is working. There are excellent NativeScript plugins available forVisual Studio, Visual Studio Code, andJetbrains-compatible IDEs, among others. Visual Studio Code in particular has handysnippets that speed up development
  • Log in to your Firebase account and find your console
  • Create a new project in the Firebase console. I named mine 'Giftler'. Also create an iOS and Android app in the Firebase console. As part of this process you'll download both a GoogleServices-Info.plist and google-services.json file. Make sure you note where you place those files, and you'll need them in a minute.

Install the dependencies

I've built Giftler as an example of an authenticated NativeScript app where users can list the gifts that they would like to receive for the holidays, including photos and text descriptions. For the time being, this app does the following on iOS and Android:
  • allows login and logout, registration, and a 'forgot password' routine
  • lets users enter gift items into a list
  • lets users delete items from a list
  • lets users edit items in the list individually by adding descriptions and photos
  • provides messaging from the Remote Config service in Firebase that can be quickly changed in the backend
Now, fork the Giftler source code, which is a complete and functional app. Once your app is cloned, replace the app's current Firebase-oriented files that you downloaded when you created your app:
  • In the /app/App_Resources/Android folder, put the google.services.json file that you downloaded from Firebase.
  • Likewise, in /app/App_Resources/iOS folder, put the GoogleService-Info.plist file also downloaded from Firebase.
These files are necessary to initialize Firebase in your app and connect it to the relevant external services.
Now, let's take a look at the package.json at the root of this app. It contains the plugins that you'll use in this app. I want to draw your attention to the NativeScript-oriented plugins:
"nativescript-angular": "1.2.0",
"nativescript-camera": "^0.0.8",
"nativescript-iqkeyboardmanager": "^1.0.1",
"nativescript-plugin-firebase": "^3.8.4",
"nativescript-theme-core": "^1.0.2",
The NativeScript-Angular plugin is NativeScript's integration of Angular. The Camera plugin makes managing the camera a bit easier. IQKeyboardManager is an iOS-specific plugin that handles the finicky keyboard on iOS. The Theme plugin is a great way to add default styles to your app without having to skin the app entirely yourself. And finally, the most important plugin in this app is the Firebase plugin.
With the dependencies in place and the plugins ready to install, you can build your app to create your platforms folder with iOS and Android-specific code and initialize the Firebase plugin along with the rest of the npm-based plugins. Using the NativeScript CLI, navigate to the root of your cloned app and type tns run ios or tns run android. This will start the plugin building routines and, in particular, you'll see the various parts of the Firebase plugin start to install. The install script that runs will prompt you to install several elements to integrate to the various Firebase services. We're going to select everything except Messaging and social authentication for the moment. A great feature is that a firebase.nativescript.json file is installed at the root of the app, so if you need to install a new part of the plugin later, you can edit that file and reinstall the plugin.
At this point, if you run tns livesync ios --watch or tns livesync android --watch to see the app running on an emulator and watching for changes, you would see a the app running and ready to accept your new login. Before you initialize a login, however, ensure that Firebase handles Email/Password type logins by enabling this feature in the Firebase console in the Authentication tab:
Let's take a look under the covers a bit to see what's happening behind the scenes. Before you can log in to Firebase, you need to initialize the Firebase services that you installed. In app/main.ts, there are a few interesting bits.
// this import should be first in order to load some required settings (like
globals and reflect-metadata)
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { AppModule } from "./app.module";
import { BackendService } from "./services/backend.service";


import firebase = require("nativescript-plugin-firebase");


firebase.init({
//persist should be set to false as otherwise numbers aren't returned during
livesync
persist: false,
storageBucket: 'gs://giftler-f48c4.appspot.com',
onAuthStateChanged: (data: any) => {
console.log(JSON.stringify(data))
if (data.loggedIn) {
BackendService.token = data.user.uid;
}
else {
BackendService.token = "";
}
}
}).then(
function (instance) {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
platformNativeScriptDynamic().bootstrapModule(AppModule);




First, we import firebase from the plugin, and then we call .init(). Edit the storageBucket property to reflect the value in the Storage tab of your Firebase console:
Now your app is customized to your own Firebase account and you should be able to register a new user and login in the app. You can edit the user.email and password variables in app/login/login.component.ts file to change the default login credentials from user@nativescript.org to your own login and password if you like.
The iOS and Android login screens
Note: you should be able to emulate your app right away on iOS, using the Xcode simulator.  On Android, make sure that you select an Android SDK emulator image that supports Google Services, such as "Google APIs Intel x86 Atom System Image." It can be hard to get the versions lined up perfectly, so pay close attention to the Firebase dependencies and version info.

Code Structure and Authentication

Angular 2 design patterns require that you modularize your code, so we will oblige by using the following code structure:
—login
  1. login.component.ts
  2. login.html
  3. login.module.ts
  4. login.routes.ts
—list …
—list-detail …
—models
  1. gift.model.ts
  2. user.model.ts
  3. index.ts
—services
  1. backend.service.ts
  2. firebase.service.ts
  3. utils.service.ts
  4. index.ts
app.component.ts
app.css
app.module.ts
app.routes.ts
auth-guard.service.ts
main.ts
I want to draw your attention to the way Firebase authentication works with the Angular 2 auth-guard.service. When Firebase is initialized in your app in app/main.ts as we saw above, the onAuthStateChanged function is called:
onAuthStateChanged: (data: any) => {
console.log(JSON.stringify(data))
if (data.loggedIn) {
BackendService.token = data.user.uid;
}
else {
BackendService.token = "";
}
}
When the app starts, check the console for the stringified data being returned by Firebase. If this user is flagged as being loggedIn, we will simply set a token which is the userId sent back by Firebase. We'll use the NativeScript application settings module, which functions like localStorage, to keep this userId available and associate it to the data that we create. This token and the authentication tests that use it, managed in the app/services/backend.service.tsfile, are made available to the app/auth-guard.service.ts file. The auth-guard file offers a neat way to manage logged-in and logged-out app state.
The AuthGuard class implements the CanActivate interface from the Angular Router module.
export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate() {
if (BackendService.isLoggedIn()) {
return true;
}
else {
this.router.navigate(["/login"]);
return false;
}
}
Essentially, if the token is set during the above login routine, and the BackendService.isLoggedIn function returns true, then the app is allowed to navigate to the default route which is our wish list; otherwise, the user is sent back to login:
const listRoutes: Routes = [
{ path: "", component: ListComponent, canActivate: [AuthGuard] },
];
Now that you have initialized your Firebase-powered NativeScript app, let's learn how to populate it with data and use Firebase's amazing realtime power to watch for the database to be updated.

Making your list, checking it twice

Starting in app/list/list.html, which is the basis of the wish list, you'll see a textfield and a blank list. Go ahead, tell Santa what you want! The items are sent to the database and added to your list in realtime. Let's see how this is done.
First, note that in app/list/list.component.ts, we set up an observable to hold the list of gifts:
public gifts$: Observable;
then, we populate that list from the database when the component is initialized:
ngOnInit(){
this.gifts$ = this.firebaseService.getMyWishList();
}
It's in the firebaseService file that things get interesting. Note the way that this function adds a listener and returns an rxjs observable, checking for changes on the Gifts collection in the Firebase database:
getMyWishList(): Observable {
return new Observable((observer: any) => {
let path = 'Gifts';
let onValueEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
console.log(JSON.stringify(results))
observer.next(results);
});
};
firebase.addValueEventListener(onValueEvent, `/${path}`);
}).share();
}
The results of this query are handled in a handleSnapshot function below, which filters the data by user, populating an _allItems array:
handleSnapshot(data: any) {
//empty array, then refill and filter
this._allItems = [];
if (data) {
for (let id in data) {
let result = (Object).assign({id: id}, data[id]);
if(BackendService.token === result.UID){
this._allItems.push(result);
}
}
this.publishUpdates();
}
return this._allItems;
}
And finally, publishUpdates is called, which sorts the data by date so that newer items are shown first:
publishUpdates() {
// here, we sort must emit a *new* value (immutability!)
this._allItems.sort(function(a, b){
if(a.date < b.date) return -1;
if(a.date > b.date) return 1;
return 0;
})
this.items.next([...this._allItems]);
}
Once the data has populated your $gifts observable, you can edit and delete elements of it and it will be handled by the listener and the front end updated accordingly. Note that the onValueEvent function of getMyWishList method includes the use ofngZone which ensures that, although data updates occur asynchronously, the UI is updated accordingly. A good overview of ngZone in NativeScript apps can be foundhere.

Remotely Configured Messages from Beyond

Another cool piece of Firebase's service includes "Remote Config", a way to provide app updates from the Firebase backend. You can use Remote Config to toggle features on and off in your app, make UI changes, or send messages from Santa, which is what we're going to do!
In app/list/list.html, you'll find a message box:
<Label class="gold card" textWrap="true" [text]="message$ | async"></Label>
The message$ observable is built in much the same way as the data list; changes are picked up in this case each time the app is freshly initialized:
ngOnInit(){
this.message$ = this.firebaseService.getMyMessage();
}
And the magic occurs in the service layer (app/services/firebase.service.ts ):
getMyMessage(): Observable{
return new Observable((observer:any) => {
firebase.getRemoteConfig({
developerMode: false,
cacheExpirationSeconds: 300,
properties: [{
key: "message",
default: "Happy Holidays!"
}]
}).then(
function (result) {
console.log("Fetched at " + result.lastFetch + (result.throttled ? "
(throttled)" : ""));
for (let entry in result.properties)
{
observer.next(result.properties[entry]);
}
}
);
}).share();
}

Publish new messages as often as you like!
Note: tinkering repeatedly with Remote Config may cause throttling of your Firebase instance, so develop with care

Take a picture!

One of the more interesting parts of this project, I think, is the ability to take a picture of your present of choice and store it in Firebase Storage. I leveraged the Camera plugin, as mentioned above, which makes managing the hardware a little easier. To start, ensure that your app has access to the device camera by getting permissions set in the ngOnInit() method in app/list-detail/list-detail.component.ts:
ngOnInit() {
camera.requestPermissions();
...
}
A chain of events begins when the user clicks the 'Photo' button in the detail screen. First,
takePhoto() {
let options = {
width: 300,
height: 300,
keepAspectRatio: true,
saveToGallery: true
};
camera.takePicture(options)
.then(imageAsset => {
imageSource.fromAsset(imageAsset).then(res => {
this.image = res;
//save the source image to a file, then send that file path to
firebase
this.saveToFile(this.image);
})
}).catch(function (err) {
console.log("Error -> " + err.message);
});
}
The camera takes a picture, and then that photo is stored as an imageAsset and displayed on the screen. The image is then named with a date stamp and saved to a file locally. That path is reserved for future use.
saveToFile(res){
let imgsrc = res;
this.imagePath =
this.utilsService.documentsPath(`photo-${Date.now()}.png`);
imgsrc.saveToFile(this.imagePath, enums.ImageFormat.png);
}
Once the 'Save' button is pressed, this image, via its local path, is sent to Firebase and saved in the storage module. Its full path in Firebase is returned to the app and stored in the /Gifts database collection:
editGift(id: string){
if(this.image){
//upload the file, then save all
this.firebaseService.uploadFile(this.imagePath).then((uploadedFile: any) =>
{
this.uploadedImageName = uploadedFile.name;
//get downloadURL and store it as a full path;
this.firebaseService.getDownloadUrl(this.uploadedImageName).then((downloadUrl:
string) => {
this.firebaseService.editGift(id,this.description,downloadUrl).then((result:any)
=> {
alert(result)
}, (error: any) => {
alert(error);
});
})
}, (error: any) => {
alert('File upload error: ' + error);
});
}
else {
//just edit the description
this.firebaseService.editDescription(id,this.description).then((result:any)
=> {
alert(result)
}, (error: any) => {
alert(error);
});
}
}
This chain of events seems complicated, but it boils down to a few lines in the Firebase service file:
uploadFile(localPath: string, file?: any): Promise {
let filename = this.utils.getFilename(localPath);
let remotePath = `${filename}`;
return firebase.uploadFile({
remoteFullPath: remotePath,
localFullPath: localPath,
onProgress: function(status) {
console.log("Uploaded fraction: " + status.fractionCompleted);
console.log("Percentage complete: " + status.percentageCompleted);
}
});
}
getDownloadUrl(remoteFilePath: string): Promise {
return firebase.getDownloadUrl({
remoteFullPath: remoteFilePath})
.then(
function (url:string) {
return url;
},
function (errorMessage:any) {
console.log(errorMessage);
});
}
editGift(id:string, description: string, imagepath: string){
this.publishUpdates();
return firebase.update("/Gifts/"+id+"",{
description: description,
imagepath: imagepath})
.then(
function (result:any) {
return 'You have successfully edited this gift!';
},
function (errorMessage:any) {
console.log(errorMessage);
});
}
The end result is a nice way to capture both photos and descriptions of the gifts for your wish list. No more excuses that Santa didn't know exactly WHICH Kylie Eyeliner to buy. By combining the power of NativeScript and Angular, you can create a native iOS and Android app in a matter of minutes. By adding Firebase you have a powerful way of storing your app's users, images and data, and a way of updating that data in real-time across devices. Cool, huh? It looks like this:
We are well on our way to create a solid wishlist management app! It remains to figure out the best way to inform Santa of our wishes - a Mailgun email integration or using push notifications would be the obvious next route. In the meantime, best wishes for a wonderful holiday season, and I hope you have a great time creating awesome NativeScript apps using Firebase!
Want to learn more about NativeScript? Visit http://www.nativescript.org. If you need help, join the NativeScript Slack channel here.
Share:

Monitoring Santa Tracker with Firebase

Doug Stevenson
Sam Stern
Developer Programs Engineer

The Santa Tracker app for Android is a Google holiday tradition. Every year, millions of people around the world use the app to play games with elves and reindeer and, of course, track Santa, as he flies around the world on December 24th. While the app is live for a few months each year, about 90% of our usage occurs in the last two weeks of December. In order to turn around improvements to Santa Tracker quickly over this time, it's critical that we can monitor and adjust the Santa Tracker app remotely. This year, we decided to go all-in with Firebase as our monitoring solution. In this blog post, I'll talk about how we use a combination Analytics, Crash Reporting, and Remote Config to maintain a high level of quality, without ever having to republish the app.

Firebase Analytics

As users navigate through the app we use Firebase Analytics events to record their behavior. Most of the mini-games in the app live in their own Activity classes, so we can use Firebase Analytics' automatic screen tracking feature to record these events without writing any code.

For events within games we use custom events to record important user actions. For example after the user finishes playing the "Penguin Swim" game, we record the event swimming_game_end with custom parameters score and num_stars. In the first week of December we noticed that 85% of users were getting zero stars when playing the Penguin Swim game. Clearly, the game is too hard, we were hoping that only 60-70% of users would get a score this low! We were able to correct this using Remote Config, which I'll talk about later.

The other feature of Analytics that we put to use is user properties. At the start of each Santa Tracker session, we use user properties to record some information about the user's device. These properties are then attached to every analytics event. Since Santa Tracker is used all over the world, we get a lot of diversity in the devices people use. These user properties help us to make sense of our analytics data. Some examples are:

API_LEVEL The API level of the user's device, like 23 for Marshmallow or 21 for Lollipop.
DEVICE_BRAND The brand of the user's device, such as Samsung for the Galaxy S7 or Google for the Pixel XL.
DEVICE_BOARD The processor or specific SoC name for the user's device.

The combination of our custom events and user properties with Firebase Analytics' automatically tracked events enables us to get a good understanding of what our users are doing in the app by looking at the Firebase console.

Firebase Crash Reporting

Despite our best efforts, the Santa Tracker app is not perfect. With millions of users on hundreds of device types in dozens of countries we are constantly discovering new bugs in the wild. Firebase Crash Reporting lets us see all of the fatal errors in our app within a minute of their occurrence. Since Firebase Analytics events show up in Firebase Crash Reporting logs we can see the progression of events before the crash which was very helpful in diagnosing some issues.

For example there's an OutOfMemoryError crash which seems to happen during the "Penguin Swim" game on some low-RAM devices. We did not see this error during our testing, but the Firebase Analytics data in Crash Reporting tells us that this occurs when playing the game repeatedly.

This integration is invaluable in helping us to reproduce issues that our normal QA setup does not find. We can get the exact device model and then use the analytics log to recreate the crash conditions.

Firebase Remote Config

Once we have analyzed the data from Analytics and Crash Reporting, we need to make changes in the app to improve the user experience. Due to the short active life span of this app there's no time to go through the full development lifecycle of the app to publish changes, and we don't get a second chance at Santa's big day!

Santa Tracker uses Firebase Remote Config to gate access to various features, and to provide remote fine-tuning for experiences in the mini game. For example, in the "Penguin Swim" game, there are two key variables we store in Remote Config:

SwimmingObstacleDensity Control the density of ice cubes and other obstacles in the game, a lower density makes the game easier.
DisableSwimmingGame A kill-switch to completely hide the game from the app.

As mentioned earlier, users were having a hard time getting a score higher than zero stars in the game. In order to make the game more fun, we changed SwimmingObstacleDensity from 1.5 to 1.1, which made it much easier for users to dodge obstacles. By making the game easier in this way, the percentage of users getting 0 stars went down from about 85% to 70%. This change took place instantly over the air, with no need to publish a new version of the app!

Right now the OutOfMemoryError in the swimming game happens for <1% of users. But if this issue became rampant, we could use the DisableSwimmingGame flag to immediately hide the game from affected users whilst we resolve the issue. By taking advantage of the fact that Analytics user properties can be referenced in Remote Config, we can even disable the game only for certain device types! For example, let's say the Penguin Swim stopped working on all KitKat devices (API level 19).

First, we add a condition based on user properties:

Next, we disable the game only for users who match the condition:

Now the game will only appear for users who will have a stable experience, which will lead to fewer crashes for our users and more positive app ratings for us.

Final Thoughts

Adding deep Firebase integration to Santa Tracker gives us the ability to monitor and fine-tune the app over time without releasing app updates. As developers, it's invaluable to have a clear picture of what our users are really doing and how we can improve the app. Throughout December we knew we could rely on Firebase to give Santa Tracker users a magical holiday experience.

Share:

Wednesday, 21 December 2016

How does Firebase initialize on Android?

Doug Stevenson
Doug Stevenson
Developer Advocate

If you've been working with Firebase on Android, you may have noticed that you don't normally have to write any lines of code to initialize a feature. You just grab the singleton object for that feature, and start using it right away. And, in the case of Firebase Crash Reporting, you don't even have to write any code at all for it to start capturing crashes! This question pops up from time to time, and I talked about it a bit at Google I/O 2016, but I'd also like to break it down in detail here.

The problem

Many SDKs need an Android Contextto be able to do their work. This Context is the hook into the Android runtime that lets the SDK access app resources and assets, use system services, and register BroadcastReceivers. Many SDKs ask you to pass a Context into a static init method once, so they can hold and use that reference as long as the app process is alive. In order to get that Context at the time the app starts up, it's common for the developers of the SDK to ask you to pass that in a custom Applicationsubclass like this:

public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SomeSdk.init(this); // init some SDK, MyApplication is the Context
}
}

And if you hadn't already registered a custom subclass in your app, you'd also have to add that to your manifest in the applicationtag's android:name attribute:

<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name="package.of.MyApplication"
... >

All this is fine, but Firebase SDKs make this a lot easier for its users!

The solution

There is a little trick that the Firebase SDKs for Android use to install a hook early in the process of an application launch cycle. It introduces a ContentProviderto implement both the timing and Context needed to initialize an SDK, but without requiring the app developer to write any code. A ContentProvider is a convenient choice for two reasons:

  1. They are created and initialized (on the main thread) before allother components, such as Activities, Services, and BroadcastReceivers, after the app process is started.
  2. They participate in manifest merging at build time, if they are declared in the manifest of an Android library project. As a result, they are automatically added to the app's manifest.

Let's investigate those two properties.

ContentProvider initializes early

When an Android app process is first started, there is well-defined order of operations:

  1. Each ContentProvider declared in the manifest is created, in priority order.
  2. The Application class (or custom subclass) is created.
  3. If another component that was invoked via some Intent, that is created.

When a ContentProvider is created, Android will call its onCreate method. This is where the Firebase SDK can get a hold of a Context, which it does by calling the getContextmethod. This Context is safe to hold on to indefinitely.

This is also a place that can be used to set up things that need to be active throughout the app's lifetime, such as ActivityLifecycleCallbacks(which are used by Firebase Analytics), or a UncaughtExceptionHandler(which is used by Firebase Crash Reporting). You might also initialize a dependency injection framework here.

ContentProviders participate in manifest merger

Manifest merge is a process that happens at build time when the Android build tools need to figure out the contents of the final manifest that defines your app. In your app's AndroidManifest.xml file, you declare all your application components, permissions, hardware requirements, and so on. But the final manifest that gets built into the APK contains all of those elements from all of the Android library projects that your app depends on.

It turns out that ContentProviders are merged into the final manifest as well. As a result, any Android library project can simply declare a ContentProvider in its own manifest, and that entry will end up in the app's final manifest. So, when you declare a dependency on Firebase Crash Reporting, the ContentProvider from its manifest is merged in your own app's manifest. This ensures that its onCreate is executed, without you having to write any code.

FirebaseInitProvider (surprise!) initializes your app

All apps using Firebase in some way will have a dependency on the firebase-common library. This library exposes FirebaseInitProvider, whose responsibility is to call FirebaseApp.initializeAppin order to initialize the default FirebaseApp instance using the configurations from the project's google-services.json file. (Those configurations are injected into the build as Android resources by the Google Services plugin.) However, If you're referencing multiple Firebase projects in one app, you'll have to write code to initialize other FirebaseApp instances, as discussed in an earlier blog post.

Some drawbacks with ContentProvider init

If you choose to use a ContentProvider to initialize your app or library, there's a couple things you need to keep in mind.

First, there can be only one ContentProvider on an Android device with a given "authority" string. So, if your library is used in more than one app on a device, you have to make sure that they get added with two different authority strings, or the second app will be rejected for installation. That string is defined for the ContentProvider in the manifest XML, which means it's effectively hard-coded. But there is a trick you can use with the Android build tools to make sure that each app build declares a different authority.

There is a feature of Android Gradle builds call manifest placeholders that lets you declare and insert a placeholder value that get inserted into manifest strings. The app's unique application ID is automatically available as a placeholder, so you can declare your ContentProvider like this:

<provider
android:authorities="${applicationId}.yourcontentprovider"
android:name=".YourContentProvider"
android:exported="false" />

The other thing to know about about ContentProviders is that they are only run in the main process of an app. For a vast majority of apps, this isn't a problem, as there is only one process by default. But the moment you declare that one of the Android components in your app must run in another process, that process won't create any ContentProviders, which means your ContentProvider onCreate will never get invoked. In this case, the app will have to either avoid calling anything that requires the initialization, or safely initialize another way. Note that this behavior is different than a custom Application subclass, which does get invoked in every process for that app.

But why misuse ContentProvider like this?

Yes, it's true, this particular application of ContentProvider seems really weird, since it's not actually providing any content. And you have to provide implementations of all the other ContentProvider required methods by returning null. But, it turns out that this is the most reliable way to automatically initialize without requiring extra code. I think the convenience for developers using Firebase more than makes up for this strangeness of this use of a ContentProvider. Firebase is all about being easy to use, and there's nothing easier than no code at all!

Share:

Friday, 16 December 2016

Working with multiple Firebase projects in an Android app

Parul Soi
Ian Barber
Developer Programs Engineer

Firebase provides a bunch of features to use together in your app, provided by a project that you create at the Firebase console. Normally, it's sufficient to have all your app's resources provided by a single project, but there are times when you want a single app to be able to access data from multiple projects. For example, you may need to access data from two different databases, and be able to authenticate users to access each one. I'll show you how that's done in this post.

First, a little terminology:
Legacy Firebase.com Project A project created on the legacy console, associated with a Firebase Database that has not been upgraded to the new console.
Google API Project A project used for accessing Google APIs - usually from https://console.developers.google.comor https://console.cloud.google.com
Firebase Project A project created on the new Firebase console. Every Firebase project is also a Google API project underneath.
App A client for a specific platform. Each project can have multiple apps associated with it..

Upgraded legacy Firebase.com project along with existing Google API project

One particular scenario occurs for developers who want to upgrade their existing legacy Firebase.com database to a new Firebase project, while also being able to use services from another Google API project. The upgraded legacy project becomes a new Firebase project, and that needs to be used in tandem with the Google API project that provides Google Sign-In authentication for existing users.
The challenge here is that for the Google Sign-In component to work on Android, it requires a SHA-1 (the fingerprint of the key used to sign the APK) and package name (e.g. com.foo.bar) to be registered for the app. This combination allows Google Sign-In to know which Google API project is being used by a particular app. A given pair of SHA1 and Package Name is globally unique within Google (and Firebase projects), so if you try to add the same pair SHA-1 and package name to an upgraded Firebase project, you get an error that the OAuth2 client already exists (in the Google API project):
Warning: If you see this, don't delete your existing client ID for apps in production! This will break your app for your existing users. The right choice is to create a new app with the same your package name in the Firebase console for the upgraded project, but not to include a SHA1.
Now implement Google Sign In with Firebase Auth as normal. At one point you will have to configure your Google Sign Options object:
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build();
The default_web_client_id string here is used to set the audience field of the ID token. The value comes from the google-services.json file, which is from the Firebase project rather than the Google project. You'll need to replace it with a client ID from the Google project. You can use any Web client ID, or create a new one:
Next, back in the Firebase project, whitelist the client ID you just set for the GoogleSignInOptions in the Auth > Sign In Providers > Googlesection of the Firebase console.
Be sure to re-download your google-services.json and add it to your Android app. At this point, your Firebase project will accept Google ID tokens generated by your Google project - so your Android app will happily sign in to Google using the Google project, then authenticate with your Firebase project using the Google ID token following the normal approach. You'll be able to make authenticated calls to Google APIs associated with the Google API project, and authenticated calls to Firebase APIs using the Firebase project.

Accessing the Databases from two different Firebase projects

In the previous situation, we had a single Firebase project which needed to also access a Google project. That works because the APIs are separate. However, sometimes you need to access different projects using the same APIs - for example, accessing multiple database instances.
For Android apps using Firebase, there is a central FirebaseAppobject that manages the configuration for all the Firebase APIs. This is initialized automatically by a content provider when your app is launched, and you typically never need to interact with it. However, when you want to access multiple projects from a single app, you'll need a distinct FirebaseApp to reference each one individually. It's up to you to initialize the instances other than the default that Firebase creates for you.
For example, to connect to the default Firebase Database instance, we implicitly use the default Firebase app:
FirebaseDatabase database = FirebaseDatabase.getInstance();
To connect to another Firebase Realtime Database from another project, you first need to initialize a FirebaseApp instance for that other Firebase project, and give it an identifier - in this case "secondary":

FirebaseOptions options = new FirebaseOptions.Builder()
.setApplicationId("1:530266078999:android:481c4ecf3253701e") // Required for Analytics.
.setApiKey("AIzaSyBRxOyIj5dJkKgAVPXRLYFkdZwh2Xxq51k") // Required for Auth.
.setDatabaseUrl("https://project-1765055333176374514.firebaseio.com/") // Required for RTDB.
.build();
FirebaseApp.initializeApp(this /* Context */, options, "secondary");

Then, you can access the database using the same client APIs, but this time specifying which project you want to access by passing the relevant FirebaseApp to FirebaseDatabase.getInstance():
// Retrieve my other app.
FirebaseApp app = FirebaseApp.getInstance("secondary");
// Get the database for the other app.
FirebaseDatabase secondaryDatabase = FirebaseDatabase.getInstance(app);

Authenticating to two different Firebase Databases

We can combine the two techniques above to allow sharing authentication data between Firebase project whenever you have an external ID to join on.
For example, if our app allows sign in with Google Sign-In, and we have configured our database rules in our default and secondary projects to require authentication, we can use the same Google credential to log in to both systems.
First, we set up the app for Google Sign-In on the default project as normal. Then, we get the underlying client ID from the default project. A client ID is just an identifier for a given app client (web, Android, iOS) that is usually contained within the client itself. A project can have several client IDs, but the one we need to whitelist is the one specified in the requestIdToken call to the GoogleSignInOptions builder:
.requestIdToken(getString(R.string.default_web_client_id))
You can usually find it as the first client_id with type "3" in the google-services.json. Mine was:
{
"client_id": "56865680640-e8mr503bun5eaevqctn4u807q4hpi44s.apps.googleusercontent.com",
"client_type": 3
},
With that in hand, we go to the Google panel of the Auth > Sign In Providers section of the secondary project, where we can whitelist the client ID.

Now we can grab the same GoogleSignInAccountobject from the Google Sign-In result and authenticate to both the default and secondary apps:
AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(), null);
FirebaseAuth.getInstance().signInWithCredential(credential);

FirebaseApp app = FirebaseApp.getInstance("secondary");
FirebaseAuth.getInstance(app).signInWithCredential(credential);
With one sign in from the user, they are authenticated against both projects.

Sharing UIDs Between Projects

One challenge here is that the Firebase user IDs on each project will be different. For example, using the same Google credential I get these two UIDs:
Default Auth UID: 0960868722032022577213DA4EA8B7A1683D92B405DD
Secondary Auth UID: 7h6XOeSxmkNsSseFJ1jU31WZHDP2
If the app doesn't offer account linking, we can use the Google (or Facebook, Twitter, etc.) user ID for things like database structures and security rules. However, if we need the same user ID in each project, or we're using email/password or anonymous auth, the situation is slightly trickier.
Luckily, it can be resolved using custom auth facilities, along with some server side code, since custom auth tokens get to specify their own UID!
This time, we don't whitelist anything on the secondary project, but we do download the service account for both it and our default projects. In our Android client, we first sign in and grab the Firebase ID token from the FirebaseAuth client:
Note: We can use any sign in provider we want here! We're just using the custom token to link our user IDs across projects.
firebaseAuth.getCurrentUser().getToken(false /* forceRefresh */)
.addOnCompleteListener(new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
String token = task.getResult().getToken(); // Send this to the server.
}
});
We send that to our server, where we use it create a Firebase custom token. Just as on Android, we need to initialise each of our apps, though we use service accounts as we're server side (here we're using the Java server SDK, but you could use NodeJS similarly).
FirebaseOptions options = new FirebaseOptions.Builder()
.setServiceAccount(new FileInputStream("default-service-account.json"))
.build();
FirebaseApp.initializeApp(options);


FirebaseOptions secondaryOptions = new FirebaseOptions.Builder()
.setServiceAccount(new FileInputStream("secondary-service-account.json"))
.build();
FirebaseApp.initializeApp(secondaryOptions, "secondary");
The primary app is used to verify the token coming from the client, and the secondary to create the custom auth token with the appropriate UID set:
// Verify the ID token using the default app.
FirebaseAuth.getInstance().verifyIdToken(idToken)
.addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(FirebaseToken decodedToken) {
String uid = decodedToken.getUid();
System.out.println("User " + uid + " verified");
FirebaseApp app = FirebaseApp.getInstance("secondary");
String customToken = FirebaseAuth.getInstance(app).createCustomToken(uid);
// TODO: Send the token back to the client!
}
});
Back in the Android app, we take the custom token from the server and use it to authenticate to the secondary project.
FirebaseApp app = FirebaseApp.getInstance("secondary");
FirebaseAuth.getInstance(app).signInWithCustomToken(token);
Now the Firebase uid in both projects matches.
Default Auth UID: 0960868722032022577213DA4EA8B7A1683D92B405DD
Secondary Auth UID: 0960868722032022577213DA4EA8B7A1683D92B405DD

For iOS and web as well

Hopefully, this helps offer some options for dealing with multiple Firebase projects from a single app. If you're wondering whether the same thing works on iOS and the web - it absolutely does. You just need to use the equivalent to Android's FirebaseApp to create a reference to the secondary project.
With JavaScript, you use firebase.app:
var config = {
apiKey: "",
authDomain: ".firebaseapp.com",
databaseURL: "https://.firebaseio.com",
storageBucket: ".appspot.com",
messagingSenderId: "",
};

var secondary = firebase.initializeApp(otherAppConfig, "secondary");
var secondaryDatabase = secondary.database();
And with iOS you use FIRApp:
// Alt: load from plist using |FIROptions(contentsOfFile:)|
let options = FIROptions(googleAppID: googleAppID, bundleID: bundleID, GCMSenderID: GCMSenderID, APIKey: nil, clientID: nil, trackingID: nil, androidClientID: nil, databaseURL: databaseURL, storageBucket: nil, deepLinkURLScheme: nil)

FIRApp.configure(withName: "secondary", options: fileopts)
guard let secondary = FIRApp.init(named: "secondary")
else { assert(false, "Could not retrieve secondary app") }

let secondaryDatabase = FIRDatabase.database(app: secondary);
For more information and links, take a look at the new Configuring Your Firebase Project page in the Firebase documentation.

Share:

Monday, 21 November 2016

Google Play services and Firebase for Android will support API level 14 at minimum

Doug Stevenson
Doug Stevenson
Developer Advocate
Originally posted on Android Developer Blog

Version 10.0.0 of the Google Play services client libraries, as well as the Firebase client libraries for Android, will be the last version of these libraries that support Android API level 9 (Android 2.3, Gingerbread). The next scheduled release of these libraries, version 10.2.0, will increase the minimum supported API level from 9 to 14 (Android 4.0.1, Ice Cream Sandwich). This change will happen in early 2017.

Why are we discontinuing support for Gingerbread and Honeycomb in Google Play services?

The Gingerbread platform is almost six years old. Many Android developers have already discontinued support for Gingerbread in their apps. This helps them build better apps that make use of the newer capabilities of the Android platform. For us, the situation is the same. By making this change, we will be able to provide a more robust collection of tools for Android developers with greater speed.

What this means for your Android app that uses Google Play services or Firebase:

You may use version 10.0.0 of Google Play services and Firebase as you are currently. It will continue to work with Gingerbread devices as it has in the past.

When you choose to upgrade to the future version 10.2.0, and if your app minimally supports API level 14 or greater (typically specified as "minSdkVersion" in your build.gradle), you will not encounter any versioning problems. However, if your app supports lower than API level 14, you will encounter a problem at build time with an error that looks like this:

Error:Execution failed for task ':app:processDebugManifest'.
> Manifest merger failed : uses-sdk:minSdkVersion 9 cannot be smaller than version 14 declared in library [com.google.android.gms:play-services:10.2.0]
Suggestion: use tools:overrideLibrary="com.google.android.gms:play_services" to force usage

Unfortunately, the stated suggestion will not help you successfully run your app on older devices. In order to use Google Play services 10.2.0 and later, you can choose one of the following options:

1. Target API level 14 as the minimum supported API level.

This is the recommended course of action. To discontinue support for API levels that will no longer receive Google Play services updates, simply increase the minSdkVersion value in your app's build.gradle to at least 14. If you update your app in this way and publish it to the Play Store, users of devices with less than that level of support will not be able to see or download the update. However, they will still be able to download and use the most recently published version of the app that does target their device.

A very small percentage of all Android devices are using API levels less than 14. You can read more about the current distribution of Android devices. We believe that many of these old devices are not actively being used.

If your app still has a significant number of users on older devices, you can use multiple APK support in Google Play to deliver an APK that uses Google Play services 10.0.0. This is described below.

2. Build multiple APKs to support devices with an API level less than 14.

Along with some configuration and code management, you can build multiple APKs that support different minimum API levels, with different versions of Google Play services. You can accomplish this with build variants in Gradle. First, define build flavors for legacy and newer versions of your app. For example, in your build.gradle, define two different product flavors, with two different compile dependencies for the components of Play Services you're using:

productFlavors {
legacy {
minSdkVersion 9
versionCode 901 // Min API level 9, v01
}
current {
minSdkVersion 14
versionCode 1401 // Min API level 14, v01
}
}

dependencies {
legacyCompile 'com.google.android.gms:play-services:10.0.0'
currentCompile 'com.google.android.gms:play-services:10.2.0'
}

In the above situation, there are two product flavors being built against two different versions of the Google Play services client libraries. This will work fine if only APIs are called that are available in the 10.0.0 library. If you need to call newer APIs made available with 10.2.0, you will have to create a compatibility library for the newer API calls so that they are only built into the version of the application that can use them:

  • Declare a Java interface that exposes the higher-level functionality you want to perform that is only available in current versions of Play services.
  • Build two Android libraries that implement that interface. The "current" implementation should call the newer APIs as desired. The "legacy" implementation should no-op or otherwise act as desired with older versions of Play services. The interface should be added to both libraries.
  • Conditionally compile each library into the app using "legacyCompile" and "currentCompile" dependencies.
  • In the app's code, call through to the compatibility library whenever newer Play APIs are required.

After building a release APK for each flavor, you then publish them both to the Play Store, and the device will update with the most appropriate version for that device. Read more about multiple APK support in the Play Store.

Share:

Thursday, 17 November 2016

Start using Firebase Test Lab for Android at no charge

Doug Stevenson
Doug Stevenson
Developer Advocate

Developer feedback continues to be a critical medium for us to ensure Firebase meets the needs of the community. Since we launched the expansion of Firebase at Google I/O in May, we received a clear message that many of you would like to try Firebase Test Labwithout having to enter a credit card number and upgrade to the paid Blaze plan. We agree, and we're proud to introduce Firebase Test Lab for Android for the Firebase Spark and Flame plans. Here's how it works.

For Firebase projects on the Spark or Flame plan, you may run up to five tests on physical devices and up to ten tests on virtual devices on a daily basis. This means you have a total budget of fifteen tests to use throughout the day. For each test run, you may target up to four different devices, and you can mix physical and virtual devices as needed. For example, you could execute five runs targeting three devices each run, using your full budget of fifteen tests for the day. Or, each of your fifteen tests could be executed on a single device in fifteen individual runs.

For projects on the Spark plan, there is no billing information required to make use of your daily quota of tests.

For both plans, the daily quota of physical and virtual device tests refreshes at 12am PST, so you can use that to schedule your daily allotment of tests. The maximum duration for tests on virtual devices is one hour, and the maximum on physical devices is thirty minutes. You can also split your allotment of tests between Robo tests, which will test your application automatically, and instrumentation tests, which allow you to script your application with Espresso, UI Automator 2.0, and Robotium.

For projects on the Blaze plan, nothing changes. You continue to run tests for $5 per device-hour on physical devices. Virtual devices can be used at no cost till the end of December (12/31/2016) then at $1 per device-hour starting 2017.

Also available now is the ability to run tests against virtual devices with Nougat MR1 at API level 25. The Firebase Test Lab team is committed to making sure you have access to the latest versions of Android, so you can spot incompatibilities early during your development.

Improving the quality of your applications is easier with Firebase. Please let us know how you're using Firebase Test Lab for Android on our support channels, including the firebase-talkmailing list, and the #test-lab channel on the Firebase Slack.

Share:

Wednesday, 29 June 2016

Introducing Firebase Test Lab for Android

Doug Stevenson
Doug Stevenson
Developer Advocate

At Google, we understand how important the quality of your application is in order to grow your user base, increase customer satisfaction and boost your revenues. When we took a closer look at data from 1-star reviews on Google Play, we observed that more than 50% of these reviews are related to bugs and crashes in the app.

Sampling of reviews in the Google Play Store, May 2016


Classification of 1-star reviews on Google Play Store
.

For many developers, finding and resolving these problems prior to release can be very challenging. As the Android ecosystem continues to grow rapidly, it can be a daunting task to maintain a high quality, performant app that scales well for your users. As your user base grows, you may only have access to a handful of devices to use for testing. Accessing devices that are unavailable in your country is also difficult. Moreover, setting up a proper testing infrastructure that facilitates pre-release testing is a very expensive and time-consuming process that requires continual maintenance.


Setting up an infrastructure with a diverse set of devices for testing

To help streamline the testing of mobile applications, we’re introducing Firebase Test Lab for Android, a platform where you can automate testing with the same tools that Google uses to test its own products. With a few easy steps, you can launch your tests on our physical devices to help ensure the best possible quality for your applications.

Your tests, our devices

Testing Android applications normally involves writing instrumentation tests to script the interactions with the app. If you’ve already written tests using Espresso, UI Automator 2.0, or Robotium, you can begin running those tests today on devices hosted by Firebase Test Lab.

Don’t write tests - record them

Authoring instrumentation tests is easier now with Android Studio 2.2 (or newer) using the new Espresso Test Recorder tool. All you have to do is launch your app in recording mode, and the test recorder will observe and remember all your interactions with the app, then generate test code in Espresso that duplicates those interactions. You can then turn around and run these tests in Firebase Test Lab.

No written tests? Use ours

Even if you’re not writing your own tests, you can still use Firebase Test Lab. We have a fully automated, intelligent test called a Robo test that will crawl your app by performing interactions with your app’s user interface. You don’t have to write a single line of code to gain the benefits of this automated coverage.

Run tests at scale - you choose the devices

With each test you run, you will select from a variety of device manufacturers and models, Android versions, and configurations (Virtual devices are also now available as a beta offering). Firebase Test Lab will then run your tests on multiple devices simultaneously to satisfy your selections as quickly as possible. When the tests are complete, the results from the test will be stored in your Firebase project.

Test early and often

Testing works best when it happens throughout the development process, not just before you publish your app on Google Play. To simplify this process, Firebase Test Lab gives you the ability to invoke tests directly from within the tools you’re already using, such as Android Studio and the Firebase Console. Our command-line interface allows you to run your tests through your continuous integration setup. Additionally, after you opt-in to receive the pre-launch report in the Google Play Developer Console, every new version of an app you publish to an Alpha or Beta channel will receive a free, five minute Robo test. These test results will become visible in the Play Store Developer Console.


Test results displayed in the Firebase console

Pricing

The Play pre-launch report generated from running the Robo tests is currently available at no cost! For more customized testing using Firebase Test Lab, customers on the Blaze Billing Plan will be charged $5 per physical device hour, and after October 1st, 2016, $1 per virtual device hour. Prior to that, virtual devices are available at no charge. See our pricing page for details.

Start improving your app quality today!

Running your first test on Firebase Test Lab is easy. You can follow our step-by-step codelab which walks you through various scenarios for running your first test. You can refer to our full documentation here. If you have questions or encounter any issues, please ask in the Firebase Google group.

Happy testing!

Share: