Working with Ionic Native - Contact Fixer

Working with Ionic Native - Contact Fixer

This post is more than 2 years old.

I've blogged a few times now about Ionic Native, but if you're new to it, you can think of "Angular2/Ionic2 Friendly Wrappers" for many different Cordova plugins. Today I'm sharing what may be my coolest demo yet. No, wait, seriously, it is, honest! This demo does something I think every phone should have built in, and if I can get off my lazy butt, I'll be submitting this to the App Store this week. So what did I build?

I noticed recently that both both iOS and Android will provide a contact picture even when you haven't selected a unique one for them. I believe, in both cases, it won't use the default picture when you get a call from them, but in the contacts app it will display it. So that's cool. I know it isn't new, but I like the fact that I can see someone's face when I get a phone call or text message from them.

However - I don't always have time to snap pictures of people when I'm adding them to my contacts. This made me curious. Given that we have a Contacts plugin and a Contacts Ionic Native wrapper, could I actually set a picture for contacts that didn't have them?

Turns out that yes, you can! And of course, that means only one thing. I could build an app to "fix" those contacts and give them better pictures. Here's how 3 of the default iOS simulator contacts are displayed:


And here are the fixed versions:


In case it is a bit too small, here is a closeup on the awesomeness:

I'm going to be so rich when I put this in the App Store. Ok, let's take a look at the app itself. First, the UI, which is incredibly simple since the app does one thing and one thing only.

On startup, we display some text explaining what we're about to do:

App start

You then click the button, it presents a 'working' message, and when done, shows you a result:

App done

And that's it. I could maybe actually show all the contacts and their new pictures, but honestly, this felt like it was enough. Let's look at the code. First, the view.


<ion-header>
  <ion-navbar>
    <ion-title>
      Contact Fixer
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  
  <p>
  This application will scan your contacts and find ones without a contact picture. For each one it finds, it will fix that problem by selecting a random cat picture. This is a <strong>one way</strong> operation
  that cannot be undone - use with caution!
  </p>

  <button ion-button color="danger" (click)="fixContacts()" full round>Make It So!</button>

</ion-content>

And now the real meat of the app, the code behind this view.


import { Component } from '@angular/core';
import { NavController, AlertController, LoadingController } from 'ionic-angular';

import { Contact, Contacts, ContactField } from 'ionic-native';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  constructor(public navCtrl: NavController, public alertCtrl:AlertController, public loadingCtrl:LoadingController) {
    
  }

  getRandomInt (min, max) {
   return Math.floor(Math.random() * (max - min + 1)) + min;
  }
  
  randomCat() {
    let w = this.getRandomInt(200,500);
    let h = this.getRandomInt(200,500);
    return `https://placekitten.com/${w}/${h}`;
  }

  //Credit: http://stackoverflow.com/a/20285053/52160
  toDataUrl(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.responseType = 'blob';
    xhr.onload = function() {
      var reader = new FileReader();
      reader.onloadend = function() {
      callback(reader.result);
    }
      reader.readAsDataURL(xhr.response);
    };
    xhr.open('GET', url);
    xhr.send();
  }

  fixContacts() {

    let loader = this.loadingCtrl.create({
      content: "Doing important work...",
    });
    loader.present();


    let fixed = 0;
    let proms = [];

    Contacts.find(["name"]).then((res) => {

      res.forEach( (contact:Contact) => {

        if(!contact.photos) {
          console.log('FIXING '+contact.name.formatted);
          //console.log(contact);

          proms.push(new Promise( (resolve, reject) => {

            
            this.toDataUrl(this.randomCat(), function(s) {

              var f = new ContactField('base64',s,true);

              contact.photos = [];
              contact.photos.push(f);
              console.log('FIXED '+contact.name.formatted);
              contact.save();
              fixed++;
              resolve();
              
            });
            
          }));
        }

      });

      Promise.all(proms).then( (res) => {
        
        loader.dismissAll();

        console.log('all done, fixed is  '+fixed);
        let subTitle, button;

        if(fixed === 0) {
          subTitle = "Sorry, but every single one of your contacts had a picture. I did nothing.";
          button = "Sad Face";
        } else {
          subTitle = `I've updated ${fixed} contact(s). Enjoy!`;
          button = "Awesome";      
        }

        this.alertCtrl.create({
            title:'Contacts Updated',
            subTitle:subTitle,
            buttons:[button]
        }).present();

      });

    });
  
  }

}

Things get kicked off when the button is clicked on the view and fixContacts() is fired. I turn on a loading component to present something to the user to let them know the app is doing something.

Next, I ask the Contacts API to return every contact. I have to pass a 'search field', even though I'm not actually passing a search value. That's a bit wonky, but that's how the plugin works, it isn't a bug in Ionic Native's implementation.

I then iterate over every contact. The photos property is empty when no pictures exist for the contact. When I find that, I kick off a process to make a new picture using the Placekitten service. I simply generate a random size and that will give me a random cat. I conver that to a base64 string and then store it in the contact.

Because this process is asynchronous, I use an array of promises I can then call then() on to know when they are all done.

Finally, I report what I did to the user using the Alert component. And that's that. Here's output from my real device. First, Max, who already had a picture.

Max

And here is Alex, who did not have a picture, but who now does, and is much improved:

Alex

You can find the complete source code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/fixcontacts

So - who would pay 99 cents for this?

Raymond Camden's Picture

About Raymond Camden

Raymond is a developer advocate for HERE Technologies. He focuses on JavaScript, serverless and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by wilson108 posted on 12/12/2016 at 6:33 PM

I would probably pay if I could customize what the actual image is, and you used @ionic/storage to remember which contacts it changed so it is reversible.

Comment 2 (In reply to #1) by Raymond Camden posted on 12/12/2016 at 7:04 PM

Well part of the idea is to not make you select an image for each, because then that's probably not much better than you doing it yourself, know what I mean? I could see offering options from the various different image placement services.

Comment 3 (In reply to #2) by jcesar posted on 12/12/2016 at 7:36 PM

maybe get it from gravatar if the contact has email account, or from a social network (if possible).
BTW, I want 10% if you use my idea

Comment 4 (In reply to #3) by Raymond Camden posted on 12/12/2016 at 7:57 PM

Oh - Gravatar is a damn good idea, but do folks still use that? It seems like it was big 5+ years ago or so, but I never hear people talking about, nor see sites really using it.

Comment 5 (In reply to #4) by jcesar posted on 12/13/2016 at 5:52 PM

I don't know, I probably configured mine 5 years ago, but some sites are still picking my image from there. I don't think people have deleted their account, so if they registered long time ago, you can still pick their old picture even if they don't use it anymore.

Comment 6 by Jose Alfonso Suarez Moreno posted on 1/10/2017 at 11:47 AM

Hello

I'm trying run this example and this error show in console:

EXCEPTION: Uncaught (in promise): TypeError: navigator.contacts is undefined

Comment 7 (In reply to #6) by Raymond Camden posted on 1/10/2017 at 11:50 AM

Did you install the plugin?

Comment 8 (In reply to #7) by Jose Alfonso Suarez Moreno posted on 1/10/2017 at 12:07 PM

Yes, the plugin is intalled.

ionic plugin add cordova-plugin-contacts

Comment 9 (In reply to #8) by Raymond Camden posted on 1/10/2017 at 12:15 PM

And you are running it via the emulator?

Comment 10 (In reply to #9) by Jose Alfonso Suarez Moreno posted on 1/10/2017 at 12:18 PM

Runing in Firefox Navigator with ionic serve command.

Comment 11 (In reply to #10) by Raymond Camden posted on 1/10/2017 at 12:21 PM

Don't. :) Run in the iOS/Android emulator or a device.

Comment 12 (In reply to #11) by Jose Alfonso Suarez Moreno posted on 1/10/2017 at 12:23 PM

Ok. Thaks. I'll try it.

Comment 13 (In reply to #11) by Jose Alfonso Suarez Moreno posted on 1/10/2017 at 1:16 PM

Dont work on emulator.

Comment 14 (In reply to #13) by Raymond Camden posted on 1/10/2017 at 1:18 PM

Do you get the exact same error? Did you modify my code?

Comment 15 (In reply to #14) by Jose Alfonso Suarez Moreno posted on 1/10/2017 at 1:28 PM

I don't modify yuor code :-(.
Alert dialog says:

Unfortunately, fixcontacts has stopped.

Comment 16 (In reply to #15) by Raymond Camden posted on 1/10/2017 at 3:10 PM

What do you see when you remote debug?

Comment 17 (In reply to #7) by Jose Alfonso Suarez Moreno posted on 1/11/2017 at 7:33 AM

Yes.

Genymotion.

I'm trying to run in device Android later.
Thanks.