Tips and tricks

Ionic 4 - ion-skeleton-text

Over the years, a lot has been said about bounce rate. In essence, you want as many people as possible to stay on your app/page for as long as possible.

One tactic you really should employ is to create something that is engaging for your users. The more they like your content, the more likely is it they will keep coming back - and stay longer.

One problem with that is the more people you compel to give you their time, the harder it becomes for your system to handle all that traffic.

You start improving your system to stay under some magical threshold of 3 seconds and all is looking good. Your app loads fast, doesn’t crash and overall performs as you want it to.

But there is always something extra you can do to can do to shorten the time from app start to app use for your user even if it’s just perceived as being shorter.

Let there be code

Let’s create a blank Ionic project and check a cool new feature in Ionic 4 - ion-skeleton-text.

> ionic start ion-skeleton-text blank

Let’s now go into the home.page.html file and add the content

<!-- home.page.html -->
<ion-content>
  <ion-list>
    <ion-item *ngFor="let item of items">
      {{ item }}
    </ion-item>
  </ion-list>
</ion-content>

We also need some content which we will display with some delay.

// home.page.ts
export class HomePage implements OnInit {
  items: Array<string>;
  constructor() {}

  ngOnInit(): void {
    setTimeout(() => {
      this.items = [
        'Berlin',
        'Buenos Aires',
        'Madrid',
        'New York',
        'Paris',
        'Sydney',
        'Tokyo'
      ];
    }, 2500);
  }
}

If you now execute

> ionic serve

you will see something like this

No loading indication

No loading indication

Adding a loading screen

In order to let the user know that something is going on in the background, we could add a loading indicator which we would remove once the data has been loaded.

// home.page.html
export class HomePage implements OnInit {
  items: Array<string>;
  constructor(private loadingController: LoadingController) {}

  ngOnInit(): void {
    this.loadData();
  }

  async loadData(): Promise<void> {
    const loading = await this.loadingController.create({
      message: 'Loading cities...'
    });

    await loading.present();

    setTimeout(() => {
      this.items = [
        'Berlin',
        'Buenos Aires',
        'Madrid',
        'New York',
        'Paris',
        'Sydney',
        'Tokyo'
      ];

      loading.dismiss();
    }, 2500);
  }
}

I won’t go into the fact that displaying a loading screen should be handled separately because that’s not the point of this post but I know you would never push code like this ;)

Now if we save and check out our browser again we will see a nice loading screen.

Loading screen

Loading screen

Let’s make it a little better

One thing that we know about loading screens is that we don’t really like them. Loading screens on games, apps or anywhere else is something that starts to annoy us quite quickly so we have to keep them as short as possible or remove them completely.

We can try to trick people into believing that the app is already in the process of displaying some content when in fact it’s still waiting for the server to respond. In some cases, it’s just enough to let your users feel like the app is working to make them happy.

When you are courting a nice girl an hour seems like a second. When you sit on a red-hot cinder a second seems like an hour. That’s relativity
— Albert Einstein

If we can display to the user something to keep them happy until they get what they came for we can keep them engaged for longer.

ion-skeleton-text is the answer

ion-skeleton-text is a new UI component which displays a placeholder content instead of the true one.

Implementing it is very straight-forward as you will see soon. Let’s dive right into it.

<!-- home.page.html -->
<ion-content>
  <ion-list *ngIf="items; else skeleton">
    <ion-item *ngFor="let item of items">
      {{ item }}
    </ion-item>
  </ion-list>
</ion-content>

<ng-template #skeleton>
  <ion-list>
    <ion-item *ngFor="let item of [50, 20, 70, 80, 50]">
      <p [ngStyle]="{ width: item + '%' }">
        <ion-skeleton-text animated></ion-skeleton-text>
      </p>
    </ion-item>
  </ion-list>
</ng-template>

What we do here is to simply show the items once they are set (after the 2500 ms) while displaying a few placeholder items in the mean-time.

We also got rid of the loading screen code again in the home.page.ts file to make it look like it was before.

// home.page.ts
export class HomePage implements OnInit {
  items: Array<string>;
  constructor() {}

  ngOnInit(): void {
    this.loadData();
  }

  async loadData(): Promise<void> {
    setTimeout(() => {
      this.items = [
        'Berlin',
        'Buenos Aires',
        'Madrid',
        'New York',
        'Paris',
        'Sydney',
        'Tokyo'
      ];
    }, 2500);
  }
}

Now if you save all you will see something like this:

Skeleton text

Skeleton text

This looks a lot more dynamic than having just a simple spinner and loading message. If you are displaying complex data you can even take advantage of two other cool Ionic component - ion-avatar and ion-thumbnail.

Let’s create one final skeleton-text item which will represent some more complex data.

<!-- home.page.html -->
<ion-content>
  <ion-list *ngIf="items; else skeleton">
    <ion-item *ngFor="let item of items">
      <ion-thumbnail slot="start">
        <img [src]="item.image" />
      </ion-thumbnail>
      <ion-label>
        <h3>City: {{ item.city }}</h3>
        <p>Country: {{ item.country }}</p>
        <p>Population: {{ item.population }}</p>
      </ion-label>
    </ion-item>
  </ion-list>
</ion-content>

<ng-template #skeleton>
  <ion-list>
    <ion-item *ngFor="let item of [1, 2, 3, 4, 5]">
      <ion-thumbnail slot="start">
        <ion-skeleton-text animated></ion-skeleton-text>
      </ion-thumbnail>
      <ion-label>
        <h3>
          <ion-skeleton-text animated style="width: 50%"></ion-skeleton-text>
        </h3>
        <p>
          <ion-skeleton-text animated style="width: 80%"></ion-skeleton-text>
        </p>
        <p>
          <ion-skeleton-text animated style="width: 60%"></ion-skeleton-text>
        </p>
      </ion-label>
    </ion-item>
  </ion-list>
</ng-template>

The typescript code has change also a little:

export class HomePage implements OnInit {
  items: Array<any>;
  constructor() {}

  ngOnInit(): void {
    this.loadData();
  }

  async loadData(): Promise<void> {
    setTimeout(() => {
      this.items = [
        { city: 'Berlin', country: 'Germany', population: '3.5 million', image: '...' },
        { city: 'Buenos Aires', country: 'Argentina', population: '15 million', image: '...' },
        { city: 'Madrid', country: 'Spain', population: '3.3 million', image: '...' },
        { city: 'New York', country: 'USA', population: '19.5 million', image: '...' },
        { city: 'Paris', country: 'France', population: '2.2 million', image: '...' },
        { city: 'Sydney', country: 'Australia', population: '5.4 million', image: '...' },
        { city: 'Tokyo', country: 'Japan', population: '9.2 million', image: '...' }
      ];
    }, 2500);
  }
}

The final result looks now like this:

A more complex example

A more complex example

Now that’s pretty, isn’t it?

The ion-skeleton-text UI component can add a little twist to your app and give the user something shiny to look at while your app does all the heavy lifting. It will keep them focused on something until the final content loads fully and you are able to display it. Giving the user the feeling that the app is running instead of waiting will make them come back and will give you more users to serve.

If you want to check out the code, have a look at it here.

Until next time,
happy coding.

Adding timestamps to photos

While working on an Ionic project, the client asked for a timestamp to be added at the bottom of a taken photo.

After some head scratching, I ended up with the solution below.

onTakePhoto(): void {
  const options: CameraOptions = {
    quality: 30,
    destinationType: this.camera.DestinationType.DATA_URL,
    encodingType: this.camera.EncodingType.JPEG,
    mediaType: this.camera.MediaType.PICTURE,
    targetWidth: 2304,
    targetHeight: 1728
  };

  this.camera.getPicture(options).then(
    (imageData) => {
      this.generateImageWithTimestamp("data:image/jpeg;base64," + imageData);
    },
    (error) => {
      // not important now
    }
  );
}

generateImageWithTimestamp(dataUrl: string): void {
  let image = new Image();
  image.onload = () => {
    let canvas = document.createElement("canvas");
    let context = canvas.getContext("2d");

    canvas.height = image.height;
    canvas.width = image.width;

    context.drawImage(image, 0, 0);

    //adding some styling
    context.font = "80px Arial bold";
    context.textAlign = "center";
    context.fillStyle = "orange";
    context.fillText(
      this.getTimeStamp(),
      image.width / 2, //center
      image.height * 0.97 //close to the bottom
    );

    const finalImage = canvas.toDataURL("image/jpeg", 1);
  };
  image.src = dataUrl;
}

getTimeStamp(): string {
  const date = new Date();

  return `${date.getDate()}. ${date.getMonth() +1}. ${date.getFullYear()}, ${date.getHours()}:${date.getMinutes()}`;
}

We simply take a photo, redraw it on a canvas where we also add the timestamp.

One thing to keep in mind here is that order is important.

In my first attempt I first added the text and then the image which, of course, has overwritten the text. It looks obvious now but it took me a little to figure it out.

Hope this will help you out.

Until next time,
Happy coding.

Deciphering error messages in Ionic

While working on some projects which use the camera to take pictures inside the app I had to add a description for the user to ask for permission.

The code for that is pretty simple:

<edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription">
   <string>Please let me take pictures</string>
</edit-config>

After that I wanted to build the application for iOS with the following command:

ionic cordova build ios

What usually finished after 10 seconds with a success message now ended like this:

Now that’s what I call an error message!

Now that’s what I call an error message!

If you are like me you would have no idea what this means. Trying to build again doesn’t solve the problem so it has to be something with this particular change.

After a little bit of digging around I found the fix.

Go into the platforms folder and into ios. There you will find ios.json which holds configurations for the entire application. Locate *-Info.plist and delete the entire object.

After that simply run:

ionic cordova prepare ios

Your build will work again and you can get on with finish your app.

Hope this helps.

Until next time,
happy coding.

Getting enum keys in TypeScript

They can be your friend..

They can be your friend..

While working on a task, my colleague stumbled upon a quirky problem.

He tried to fetch all keys out of an enum but ended up with both the keys and values. We tried figuring out why this happens and once we saw the compiled javascript everything was clear.

Let’s have a look:

enum SomeEnum { 
    A,
    B,
    C,
    D
}

The enum above will be transpiled into:

var SomeEnum;
(function (SomeEnum) {
    SomeEnum[SomeEnum["A"] = 0] = "A";
    SomeEnum[SomeEnum["B"] = 1] = "B";
    SomeEnum[SomeEnum["C"] = 2] = "C";
    SomeEnum[SomeEnum["D"] = 3] = "D";
})(SomeEnum || (SomeEnum = {}));

The thing here that is important is to realize that with this output the final object will have A, B, C, and D as keys as well as 0, 1, 2, and 3.

Because of the SomeEnum[“A”] = 0 assignment we will create a property “A” with the value of 0 and then (as part of the larger SomeEnum[SomeEnum["A"] = 0] = "A"; assignment) create a property “0” with the value of "A” .

If you try to get the keys (with Object.keys()) you will get the mentioned A, B, C, D, 0, 1, 2, and 3.

To get only they keys or member names you can do something like this:

let enums = Object.keys(SomeEnum).filter(x => !(parseInt(x) >= 0));
console.log("Enums", enums); //Enums: A, B, C, D

Hope this will help you out in your TypeScript quest.

Until next time.

Happy Coding!

Error when creating a new Ionic project - ZlibError

Now that Ionic 4(!!!) is out I wanted to take it for a ride. I opened up VS Code and entered the following command into the terminal

> ionic start ionic4test tabs

After a second or two the process stopped with the following error

- Downloading and extracting tabs starter (100.00%)
events.js:193
      throw er; // Unhandled 'error' event
      ^

ZlibError: zlib: Cannot read property 'length' of null
    at Unzip.Zlib.(anonymous function) (C:\Users\Ivan Karacic\AppData\Roaming\npm\node_modules\ionic\node_modules\minizlib\index.js:126:21)
    at Unzip.write (C:\Users\Ivan Karacic\AppData\Roaming\npm\node_modules\ionic\node_modules\minizlib\index.js:247:21)
    at Unzip.flush (C:\Users\Ivan Karacic\AppData\Roaming\npm\node_modules\ionic\node_modules\minizlib\index.js:206:10)
    at Unzip.end (C:\Users\Ivan Karacic\AppData\Roaming\npm\node_modules\ionic\node_modules\minizlib\index.js:213:10)
    at Unpack.end (C:\Users\Ivan Karacic\AppData\Roaming\npm\node_modules\ionic\node_modules\tar\lib\parse.js:416:21)
    at IncomingMessage.onend (_stream_readable.js:598:10)
    at Object.onceWrapper (events.js:322:30)
    at emitNone (events.js:120:20)
    at IncomingMessage.emit (events.js:218:7)
    at endReadableNT (_stream_readable.js:1054:12)

I first tried updating the minizlib Node module but that didn’t help.

After some random web searches I found out what the issue is.

A simple upgrade to a 10+ Node.js version did the trick.

It took me around 30 minutes to figure this one out so I hope that this might help you get to the answer a little quicker.

Until next time,
happy coding.

Passing data back from a child page (Ionic tips and tricks)

When working with Ionic (3), I came across the need to pass between pages.

Full steam ahead!

Here is what the Ionic docs have to say about it:

push(page, params, opts) 

//Push a new component onto the current navigation stack.
//Pass any aditional information along as an object.
//This additional information is accessible through NavParams

Let’s try it out!

export class StartPage {
    constructor(public navCtrl: NavController) {
    }

    pushPage(): void {
        this.navCtrl.push(OtherPage, {
          id: "123",
          name: "Carl"
        });
    }
}

class OtherPage {
    constructor(private navParams: NavParams) {
        let id = navParams.get('id');
        let name = navParams.get('name');
    }
}

As you can see above, it’s quite straight forward. You push a new component on the navigation stack and pass an object as your navigation parameter and then get those parameters in the child page.

Going back the other way

There might be situations where you want to pass data from your child page to the parent. The user might select something and you want this information in the parent to fill out a form for example.

That’s where things get a little tricky.

Here the documentation say:

pop(opts) 
//Call to navigate back from a current component.
//Similar to push(), you can also pass navigation options.

However, those navigation options don’t offer you all that much

Property   Value    Description
animate    boolean  Whether or not the transition should animate.
animation  string   What kind of animation should be used.
direction  string   The conceptual direction the user is navigating. For example, is the user navigating forward, or back?
duration   number   The length in milliseconds the animation should take.
easing     string   The easing for the animation.

We will have to come up with some other way of passing data back to the parent.

The bad

Two approaches I don’t like at all are: having a global object and having some kind of pub/sub mechanism.

When you maintain a global object which will keep informations for all pages to be read from things can get quite complicate. Once your application gets quite large this starts to look like a mess and having a single file grow to a few hundreds or even thousands of lines (I have worked once on a project that had a globalSettings object was a nightmare) is not really something to look up to.

Another approach is to have some kind of pub/sub functionality where one page would publish/broadcast something and then all other views would just pick it up update their state. I don’t really like this approach since you, again, rely on a single object to take care of everything.

The good

One solution I like quite a lot is the use of Promises. You pass the resolve callback as a parameter to the child view and once the view pops you execute the resolve callback and pass data to the parent.

Your code could look something like this:

export class StartPage {
    constructor(public navCtrl: NavController) {
    }

    pushPage(){
        new Promise((resolve, reject) => {
            this.navCtrl.push(OtherPage, {
                id: "123",
                name: "Carl",
                resolve: resolve
            });
        }).then((data) => {
          console.log(data); // 'some data'
        });
    }
}

class OtherPage {
    private resolve: Function;
    constructor(private navParams: NavParams) {
        let id = navParams.get('id');
        let name = navParams.get('name');
        resolve = navParams.get('resolve'); 
    }
    
    returnData(): void {
        this.navCtrl.pop().then(() => this.resolve('some data'))
    }   
}

This looks nice and there isn’t a lot of overhad involved but, unfortunately, couples your parent its child.

My favorite approach

One thing that bugs me about the above approach is the new Promise we have to create and then wire up to get it working. That’s why the next approach is by far my favorite:

export class StartPage {
    constructor(public navCtrl: NavController) {
    }

    pushPage(): void {
        this.navCtrl.push(OtherPage, {
          id: "123",
          name: "Carl",
          callback: this.handleChildPage
        });
    }
    
    handleChildPage(data): void {
        console.log(data); // 'some data'
    }
}

class OtherPage {
    private callback: Function;
    constructor(private navParams: NavParams) {
        let id = navParams.get('id');
        let name = navParams.get('name');
        callback = navParams.get('callback'); 
    }
    
    returnData(): void {
        this.callback('some data');
        this.navCtrl.pop();
    }   
}

It doesn’t look much different than the solution with promises but I like this one a little more because we have avoided two .then(…) statements which make the code look a little more cluttered than I like. But, just as the example above, this one also couples your two views and that’s something we will have to live with (for now).

The ideal solution

I am looking forward where we will be able to pass navParams into the pop function and then just pick them up in the parent and use them.

Let me know about your approaches in the comments below.

Until next time,

Happy coding