It doesn’t just take a good idea when it comes to making a great mobile application. Sometimes it takes a little flair in the user interface department to make a good mobile application into a great mobile application. For example, when you have a user login screen, you could just ask for username and password credentials, or you could include profile picture information as well. Which do you think would be better?
Gravatar offers a global avatar registry based on user email address. In other words, you upload your profile picture to Gravatar, associate it with your email address, and then that same avatar can be used in any application that uses Gravatar. WordPress, the platform this blog is hosted on, uses Gravatar for my profile picture for example.
We’re going to see how to automatically set the user profile picture in a NativeScript Angular application using the Gravatar API.
The goal here is to use a mixture of techniques to make your application a little more memorable. Take the following animated image of two emulators, for example:
In the above image we have an Android and an iOS emulator both running a NativeScript application. By default it shows the Gravatar placeholder image. When a matching email address is used, the placeholder image changes through an animation. The matching avatar is retrieved through the Gravatar API.
To keep things easy to understand, we’re going to create a fresh NativeScript project for Android and iOS that uses Angular and TypeScript.
From the Command Prompt (Windows) or Terminal (Linux and Mac), execute the following:
tns create GravatarProject --ng
cd GravatarProject
tns platform add ios
tns platform add android
If you’re not using a Mac with Xcode installed, you won’t be able to build and deploy for the iOS platform.
The Gravatar API requires all email addresses be hashed using the MD5 algorithm before sending them over HTTP via a request. JavaScript doesn’t have any functions for this included out of the box so we’ll have to download a library for the job.
From the command line, execute the following:
npm install blueimp-md5 --save
The above command will install the blueimp MD5 JavaScript library. Because of it’s simplicity, we won’t bother trying to find TypeScript type definitions for it.
The goal of our logic is to hash emails and send them to Gravatar in hopes of getting an image back. We don’t need to do Angular HTTP requests to make this happen based on the format of the API.
Take the app/app.component.ts file for example:
import { Component } from "@angular/core";
var MD5 = require("blueimp-md5");
@Component({
selector: "my-app",
templateUrl: "app.component.html"
})
export class AppComponent {
public picture: string;
public email: string;
public constructor() {
this.email = "";
this.picture = "https://www.gravatar.com/avatar/00000000000000000000000000000000?s=150";
}
public getProfilePicture(email: any) {
if(this.validateEmail(email)) {
this.picture = "https://www.gravatar.com/avatar/" + MD5(email) + "?s=150";
}
}
// Taken from http://stackoverflow.com/a/46181/498479
private validateEmail(email: any) {
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
}
There isn’t much going on in the above code, but we’re going to break it down anyways.
After importing the Angular and MD5 components we enter the AppComponent
class. In this class we have two public variables, one which will hold a link to a Gravatar image, whether it be placeholder or real, and one to hold the current email address.
In the constructor
method we initialize these variables. The initial picture
variable will be a link to the Gravatar placeholder image.
Notice the validateEmail
method. I didn’t create this code, but instead I picked it off of Stack Overflow. What it does is checks to see if an email address is valid using regular expressions. We want this function because we only want to send valid emails to Gravatar to cut down on network requests.
public getProfilePicture(email: any) {
if(this.validateEmail(email)) {
this.picture = "https://www.gravatar.com/avatar/" + MD5(email) + "?s=150";
}
}
In the above getProfilePicture
method we pass an MD5 hashed, valid email, to Gravatar. If it doesn’t exist we’ll get a placeholder image back, otherwise we’ll get a correct image back.
This project will use an input form for emails and by default in Angular there is no data binding available. We have to include a particular forms module to make this possible.
Open the project’s app/app.module.ts file and include the following code:
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/platform";
import { NativeScriptFormsModule } from "nativescript-angular/forms";
import { AppComponent } from "./app.component";
@NgModule({
declarations: [AppComponent],
bootstrap: [AppComponent],
imports: [NativeScriptModule, NativeScriptFormsModule],
schemas: [NO_ERRORS_SCHEMA]
})
export class AppModule { }
In the above we’ve just imported the NativeScriptFormsModule
and added it to the imports
array in the @NgModule
block.
The code so far is quite basic, but it gets the job done. However, we want to fancy it up and get some animations into the mix.
There are several ways to add animations to a NativeScript mobile application, but for best control it makes sense to use Angular animations. These animations will extend on our TypeScript code.
Open the project’s app/app.component.ts file and include the following in the @Component
block:
@Component({
selector: "my-app",
templateUrl: "app.component.html",
animations: [
trigger("state", [
transition("void => *", [
animate(200, style({
transform: 'scale(2, 2)'
})),
]),
transition("* => *", [
animate(500, keyframes([
style({ transform: 'scale(2, 2)' }),
style({ transform: 'scale(0, 0)' }),
style({ transform: 'scale(2, 2)' }),
]))
]),
])
]
})
Above defines a set of animations called state
that don’t actually have states in them. Instead we have transition instructions only.
When the component is void
, or in other words not rendered to the screen and that component status changes, it will be scaled by 200% over the span of 200 milliseconds. When the state changes to anything other than void
to wildcard, we have keyframe animations. The keyframes scale from 200% to 0% and back to 200% over the span of 500 milliseconds. The animation will look like a pulse.
To change the non-defined states we need to create a boolean or some other variable to represent the current state:
public currentState: boolean;
Within the constructor
method we can initialize this variable to false. When using a valid email against the getProfilePicture
we can toggle the state to trigger the animation.
The full app/app.component.ts file would look like this:
import { Component, trigger, state, transition, animate, style, keyframes } from "@angular/core";
var MD5 = require("blueimp-md5");
@Component({
selector: "my-app",
templateUrl: "app.component.html",
animations: [
trigger("state", [
transition("void => *", [
animate(200, style({
transform: 'scale(2, 2)'
})),
]),
transition("* => *", [
animate(500, keyframes([
style({ transform: 'scale(2, 2)' }),
style({ transform: 'scale(0, 0)' }),
style({ transform: 'scale(2, 2)' }),
]))
]),
])
]
})
export class AppComponent {
public picture: any;
public email: string;
public currentState: boolean;
public constructor() {
this.email = "";
this.picture = "https://www.gravatar.com/avatar/00000000000000000000000000000000?s=150";
this.currentState = false;
}
public getProfilePicture(email: any) {
if(this.validateEmail(email)) {
this.currentState = !this.currentState;
this.picture = "https://www.gravatar.com/avatar/" + MD5(email) + "?s=150";
}
}
// Taken from http://stackoverflow.com/a/46181/498479
private validateEmail(email: any) {
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
}
This just leaves us to creating the XML and CSS based UI for the project.
What happens next will span two different project files. The basic UI will be a screen with two sections positioned vertically. The top section will be the profile picture and the second section will be the form.
Open the project’s app/app.component.html file and include the following XML markup:
<ActionBar title="{N} Gravatar"></ActionBar>
<GridLayout rows="*, *" columns="*">
<Image [src]="picture" [@state]="currentState ? 'active' : 'inactive'" height="75" width="75" class="img-rounded" row="0" col="0"></Image>
<StackLayout class="form" row="1" col="0">
<StackLayout class="input-field">
<Label class="label" text="Email:"></Label>
<TextField class="input input-border" [(ngModel)]="email" (ngModelChange)="getProfilePicture($event)" autocapitalizationType="none"></TextField>
</StackLayout>
<StackLayout class="input-field">
<Label class="label" text="Password:"></Label>
<TextField class="input input-border" secure="true"></TextField>
</StackLayout>
</StackLayout>
</GridLayout>
The GridLayout
lets us split the screen into sections. I did previous article on GridLayout
stretching, but essentially using a wildcard for each row means space them equally.
The first row is our image which is bound to the picture
variable in the TypeScript code. The [@state]
chunk is the state
animation and it holds what our current state is. Remember, as long as it changes the animation will trigger. It doesn’t really matter what it changes to in our scenario.
The second row is a form with several input fields. The first input field is bound to our email
variable and every time the data changes, the data is passed to the getProfilePicture
method.
I’m not much of a fan when it comes to the default padding of input fields so I’ve defined my own in the app/app.css file:
.form .input-field .input {
padding: 5;
background-color: #F0F0F0;
}
@import 'nativescript-theme-core/css/core.light.css';
The above CSS will add padding and a background to every text field.
You just saw how to use Gravatar and Angular to automatically set a profile picture based on email addresses within your NativeScript Android and iOS application. While not totally necessary within a mobile application, it does make the application user experience a bit more fancy. Additionally it teaches you how to use Angular animations and change events on input fields.
A video version of this article can be seen below.