Case Study: Realtime Image Sharing App with Ionic and Firebase Last update: 2017-07-11

Case Study: Realtime Image Sharing App with Ionic and Firebase

We often go through various code examples for different use cases with Ionic here, but we seldom talk about building real world apps, where we most certainly encounter different problems then inside our little sandbox tutorials. Let’s talk about a simple image sharing app with Firebase!

Inside this post I’ll give you an overview about a recent Ionic app I developed. There are always different phases when you take a new project, and each of these stages is important (besides the blocker phase, nobody needs that one).

If you want you can also check out the app below and leave me a good rating :)

iOS: https://itunes.apple.com/de/app/picu/id1247256765 Android: https://play.google.com/store/apps/details?id=com.devdactic.picu

The Idea Phase of the Image Sharing App

This year my fiancé and I are getting married, and we wanted an easy way for all guests of our wedding to share the pictures they have taken with us.

We don’t want to collect each image from everyone afterwards or ask what they have taken at the party, we just wanted one place where everyone could upload their images on their own so other people can see these images and download them.

Sounds pretty easy, right?

It isn’t that hard, but I also wanted to make this a bit more “white-label” or a general solution and not only specific for weddings.

We all have some big events, from work, birthday parties or whatever happens where we take images. And we don’t need another spammy WhatsApp group with people we actually don’t know.

Therefore, this idea needed to become a solution to this problem.

The Feature Outline

As you don’t want random people to see your images, you need some sort of authentication.

Once people are inside the app, they should be able to join groups and, what I initially left out, also create groups.

Inside a group they should be somehow able to share from their library or take an image and add an optional description if they want to.

The image below was the actual guideline I wrote myself inside my notes app. wedding-app-outline All the clear?

Sounds like a funny little app to work on!

BTW: Before you ask, my current note taking app is Bear. I left Evernote because Bear felt a bit more lightweight and so far I love it.

The Technology Stack

You might have already seen it inside the last image, my technology selection was Firebase (+ Ionic of course).

I am not so skilled that I can come up with a fast solution for this idea on my own, and Firebase is offering exactly what we need:

  • Authentication: Easily create users or use a social login
  • Database: Store information to these groups, users and images
  • Storage: Upload the images taken by users to the Firebase Storage

So all we need is out there, we just need to use it! And although Jorge is the Firebase expert, I somehow managed to get everything working in the end!

Firebase Data Structure for Image Sharing

Structuring your data in the right way is a critical point when using Firebase. I’m not suer if this way is right, but it felt good at most stages so I’m quite comfortable with it.

Inside the Database we have 2 major groups: groups and userProfile.

After registration, the profile for each user is created with it’s firebase UID and his name and email is saved. Also, whenever a user joins an image group this group is added to his own groups. wedding-app-profile

Inside that object the key and the clear name of that group is stored. This helps us to show a list to the user with all of his groups without making a big query over the other object inside the database!

But that node was kinda easy, the hard thing was implementing the groups. The groups need some information about themselves and they need to have multiple images.

Each image has again some specific information, at the time writing this I think perhaps the images could be completely separate from group so the groups object is not getting to fat.. Any thoughts on that? wedding-app-groups

This was my final solution which works until now also very good. Until now we have been very theoretical, so let’s see some code highlights!

Implementation Highlights

The overall structure of the app is not really hard. From the image below you can see most of my files, all the logic concerning firebase is inside one provider. I used a progress bar inspired by Josh and a validator for email validation from Jorge. Follow those blogs if you are not doing it by now!

wedding-app-structure

Image Thumbnails & Upload

One of the most challenging tasks was the creation of a thumbnail I haven’t found a good solution so I hacked together something using the canvas. If you are interested in my solution, let me know below and I’ll make a new tutorial from that because there is quite some code involved..

Also, uploading the images and updating the data needed to be chained somehow to cover everything in one big async action. The code to upload the image data looked for example like this:

uploadImageData(imageData, meta) {
    let imgName = this.firebaseProvider.user.uid + '_' + new Date().getTime();

    this.createThumbnail(imageData, _data => {
      let cleanedData = imageData.replace('data:image/jpeg;base64,', '');
      let cleanedDataPrev = _data.replace('data:image/jpeg;base64,', '');
      this.uploadActive = true;

      let uploadTask = firebase.storage().ref(this.groupKey).child(imgName + '.webp').putString(cleanedData, 'base64', { contentType: 'image.webp' });

      uploadTask.then(data => {
        let fullUrl = data.downloadURL;
        let uploadThumbTask = firebase.storage().ref(this.groupKey).child(imgName + '_prev.webp').putString(cleanedDataPrev, 'base64', { contentType: 'image.webp' });

        uploadThumbTask.then(res => {
          let thumbUrl = res.downloadURL;
          let imgInf = {
            'imageURL': fullUrl,
            'thumb': thumbUrl,
            'owner': this.firebaseProvider.user.uid,
            'createdAt': new Date().getTime(),
            'username': this.userName,
            'desc': meta['desc'],
            'place': meta['coords']
          }
          this.firebaseProvider.storeImageData(this.groupKey, imgInf);
        });
      });

      uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, snapshot => {
        var percent = snapshot.bytesTransferred / snapshot.totalBytes * 100;
        this.loadProgress = Math.floor(percent);
        if (this.loadProgress === 100) {
          this.uploadActive = false; +
            this.presentToast('Neues Bild hochgeladen!');
        }
      });
    });
  }

There is a solution for everything! It took a few hours but if you implement one step after each other it just works.

Also I hooked into the STATE_CHANGED to get the upload progress of the task to upload our progress bar!

Of course you can’t see all the other functions now, if you want to see more code of this or specific parts just let me know, I could come up with like 5 new tutorials just around the learnings of this app.

This is also a reason why I added learning projects and challenges to the Ionic Academy: Once you actually put stuff into action and hit problems, you learn a lot faster!

Joining Groups

To join groups we also needed some functions and some of them were Observables, some Promises, and some a special Firebase Observable which could not be converted to a Promise easily. For that problem I found a great solution to convert a Thenable to Observable here.

addUserToGroup(groupName, groupKey): Observable < any > {
  let groupData = {
    [groupKey]: groupName
  }

    return this.afd.object('/userProfile/' + this.user.uid + '/groups/' + groupKey)
    .take(1)
    .flatMap(obj => {
      if (obj.$exists()) {
        return Observable.create(observer => {
          observer.next({ success: false, msg: 'Du bist bereits Mitglied dieser Gruppe!' })
          observer.complete();
        })
      } else {
        let obs = Observable.fromThenable(this.afd.object('/userProfile/' + this.user.uid + '/groups/' + groupKey).set(groupName));
        return obs.flatMap(res => {
          let data = {
            [this.user.uid]: true
          }
          return this.afd.object('/groups/' + groupKey).update(data).then(res => {
            return { success: true, msg: 'Du bist erfoglreich der Gruppe beigetreten' };
          });
        })
      }
    });
}

Although you won’t understand the German messages, you can see from the success field if the result is ok. If you want some kind of response to a user like showing a toast you need to handle all this async stuff and still pass the right result in the end back, so it’s here a combination of take() and flatMap() and other stuff to achieve the final result (hell this took me long..).

You can really do all kind of freaky stuff with Rxjs so getting a good understanding of all the functions is definitely recommended!

Custom Styling

Of course we don’t want the out of the box styling when we build a real world app, so using Ionic Theming makes it really easy to change every part of your app.

One piece that was not working so super easy was overriding the header bar color with a gradient. In my case, the solution looks like this:

.header-ios .toolbar-background-ios {
  background: linear-gradient(to right, #6541e8 10%,#cf48c6 100%);
}

.header-md .toolbar-background-md {
  background: linear-gradient(to right, #6541e8 10%,#cf48c6 100%);
}

The problem is that we cannot set this gradient function to the Sass value Ionic offers for styling the header. Meh!

Besides that, the rest of thy styling was easy going. Especially making a modal with transparent background can give you really great results!

Development Blockers

All of what I described was solved in some way and took a reasonable amount of time (hello Rxjs!), but 2 super specific errors followed me until the end through the development.

Uncaught (in promise): removeView was not found

For some reasons, pages were created and removed to fast and I always tried to show a loading indicator when asynchronous operation was going on and dismissed it when it was finished.

But sometimes this error came up every now and then: weddin-app-remove.view

The solution here was two folded. First, make sure that you are showing and dismissing the loading correctly and not too often. But what’s more important, is this piece of code:

presentLoading() {
  this.loader = this.loadingCtrl.create({
    dismissOnPageChange: true
  });
  this.loader.present();
}

By adding dismissOnPageChange to the creation of each loading Ionic takes care of dismissing it when the page leaves. You don’t always have to do everything on your own!

Cannot read property ‘push’ of null

The second one was more related to Firebase and active subscriptions. Also, it just came up like every second time when loading but it was still nasty. wedding-app-subscription-error

The problem is that you are sometimes still subscribed to an Observable when the page leaves, leaving something behind that should not be there.

The solution was to store our subscription and to manually take care of unsubscribing once the ionViewWillUnload event is called like this:

this.subscription = this.groups.take(1).subscribe(data => {
          // Some action...
});

ionViewWillUnload() {
  if (this.subscription) {
    this.subscription.unsubscribe();
  }
}

Perhaps not the most obvious solution, but it’s ok to sometimes take care of your own garbage, right?

Submission Process

I wont cover the full submission process here, there is just one thing that helps me all the time when it comes to creating the store entries:

Sketch to App Store template FTW!

With this Sketch template, you can take a few screenshots, select a background image and add some texts to a few fields and the template will create all of your screenshots for both Android and iOS!wedding-app-sketch

I don’t know how many hours this has saved me, so give it a try next time you rage about all the app screenshot sizes. But of course you need the paid app Sketch for this.

The Result

I’m really happy with the result and hope everything works on our wedding day. Perhaps you can give the app a try and report if you find any mature bugs! The links are again below.

iOS: https://itunes.apple.com/de/app/picu/id1247256765 Android: https://play.google.com/store/apps/details?id=com.devdactic.picu

I hope this post gave you a good idea of a “real world” app besides all code examples and dummy todo lists!

Building your own stuff helps you to learn everything so much faster. Of course it takes time, but you have to enjoy the process!

You hit problems, you find solutions - and that’s what we really like about coding, isn’t it?