BLOG

14
Jan

Ionic – Closing $ionicLoading if your app loses network connectivity.

When loading data to a screen in your app, you have to have some way of notifying the user of what’s happening. You just have to. Not doing so provides a poor user experience, and ultimately leads to the user becoming impatient and/or frustrated with the app, because it doesn’t seem to behaving in a way that they’re expecting.

This isn’t the experience you want to provide your users with.

Conveniently, Ionic, yet again, have provided us with a simple solution to improve the overall UX: $ionicLoading. See here for the documentation.

$ionicLoading is basically a popover that displays a message whilst data is loading. The only issue with this, however, is that when the message is displayed, the user cannot perform any further actions within the app – they have to wait for the data to load. This works fine when you can guarantee that the app will remain connected to a network, but if you lose connectivity, then the $ionicLoading popover is displayed indefinitely (or until you kill the app). Also, a user may just want to continue to use the app while the data is loading, which is again not possible.

We hunted around, but couldn’t find a suitable alternative to $ionicLoading, so we eventually decided to create our own $ionicLoading template. This was essentially the same style as the default popover, but contained a close button – providing the user with the ability to interact with the app, if desired.

Right, enough yakking, and lets get into some code!

First lets create the HTML for the template…

<!-- Setting up a row/col to position the close button -->
<div class="row row-style">
  <div class="col col-10 col-offset-90 col-style">
    <button class="button button-clear button-style" ng-click="closePodcastsLoader()">
    <i class="ion-close-circled"></i>
    </button>
  </div>
</div>
<p>Loading Podcasts...</p>

And our CSS…

.row style {
padding-bottom:0; 
margin-bottom:0; 
margin-top:-19px;
}

.col-style{
text-align:right;
}

.button-style{
line-height: normal;
min-height: 0; 
min-width: 0;
}

As you can see, the template contains HTML and CSS for the positioning of the close button, and for the message that we want to display on the popover. We’ll save this template as popover-template.html in the templates folder (‘templates/popover-template.html’).

Next, the controller code…

.controller('DataLoadingCtrl', function($scope, $ionicLoading) {
  $scope.loadData = function() {
  // function to load some data here, then show the popover with...
    $ionicLoading.show({
      templateUrl: 'templates/popover-template.html',
      scope: $scope
    });
  }
  // function to close the popover...
  $scope.closePopover = function() {
    $ionicLoading.hide();
  }
});

In the controller code we have a function, that when executed, loads some data and displays the $ionicLoading popover whilst this data is loading. You’ll notice that in the $ionicLoading.show() function, we define our templateUrl property as our HTML template.

We then have a $scope.closePopover() function that’s executed when the close button on the popover is ng-clicked. The user now has the freedom to do whatever they want when the $ionicLoading popover is shown.

And that’s it! A simple workaround for a problem that gave us a headache for a little while.

07
Jan

Ionic – Range slider when playing a file with ngCordova’s $cordovaMedia

It’s common knowledge that all good media players can only be considered dev complete, AFTER the implementation of a range slider. Without a range slider, you might as well just toss that shit in the trash, ‘cos ain’t nobody got time fo’ dat. Don’t be that guy. Implement a ranger slider. The following blog post should help you on your way.

In another post we described how to play a media file. The following HTML would go in the same template file as the HTML does in that post.

Template HTML…

 <div class="range range-positive">
    <input type="range" name="podcast-position" min="0" max="{{model.media.length}}" ng-model="model.currentPos" ng-change="sliderPositionChange()"> 
 </div>

The above HTML specifies a start point and an end point for the media, using: min and max, where ‘min’ is the point position that the media will start, and ‘max’ is the length of time to the end of the media.
Since it’s a range slider, we’d obviously want to be able to change the play position of the media by moving the range slider itself. To do this, we need an ng-change and a function that changes the media’s play position: sliderPositionChange().
Ng-model simply tracks the position of the media based on the position of the range slider – it’s position is stored in the model.currentPos variable.

Now we’ve got that out of the way, let’s look at the ng-change function that will allow our range slide to work.

Controller code…

.controller('PlayCtrl', function($scope, $ionicPlatform, $cordovaMedia) {<del datetime="2015-01-21T23:10:51+00:00">
   
   $scope.model = {}; 

   $interval(function() {
     $cordovaMedia.getCurrentPosition(media).then(function(res){
       $scope.model.currentPos = res;
     }); 
   },1000);

   $scope.sliderPositionChange = function() {
     var mediaInMilli = $scope.model.currentPos*1000;
     media.seekTo(mediaInMilli);
   };
});

The most important part about the above code is the $interval function. Inside it we get the current position of the media with $cordovaMedia.getCurrentPosition(media), and assign it to the $scope.model.currentPos variable. The interval period is set at 1000 milliseconds. Now, every second, the $scope.model.currentPos variable will get updated, and since the ng-model of our range slider is model.currentPos, the range slider will update every second too.

The $scope.sliderPositionChange() function above is called when the range slider is moved. When the range slider is moved the ng-model=”model.currentPos” variable is updated and passed into the ng-change="slider.PositionChange()" function (see HTML).
The code inside the $scope.sliderPositionChange function is almost identical to the rewind/fast-foward code in the previous post… The model.currentPos variable is in seconds, the $cordovaMedia.seekTo() function needs the position to be in milliseconds, so we just multiply model.currentPos by 1000, and pass that into the seekTo() function.

Done. So simple, i think everybody got time fo’ dat.

01
Jan

Ionic – Playing a media file with ngCordova’s $cordovaMedia

If you’ve read any of our previous posts, you’ll already have an understanding of how to download a file to your device (and display it), using $cordovaFile. Now that you have the file, let’s play it.

As with the majority of the other posts, ngCordova do most of the legwork for us with their $cordovaMedia plugin.

Plugin installation…

cordova plugin add org.apache.cordova.media

Now, we’ll assume you already have a media file listed and displayed on your app, and get straight into the HTML.

Template Code…

<div class="row" style="padding-top: 0;">
  <div class="col play-buttons-center">
    <button class="button ion-ios-rewind-outline">Rewind</button>
  </div>
  <div class="col play-buttons-right">
    <button class="button ion-ios-play-outline">Play</button>
  </div>
  <div class="col play-buttons-left">
    <button class="button ion-ios-pause-outline">Pause</button>
  </div>
  <div class="col play-buttons-center">
    <button class="button ion-ios-fastforward-outline">Fast Forward</button>
  </div>
</div>

Above we have 4 buttons necessary for playing a file: Rewind, Play, Pause, and Fast Forward. When we press play the play() function is called and we pass through the download that we wish to play. The others buttons (and their functions will just work once a media object is established). Simple stuff. Now let’s look at the Controller code.

Controller code…

.controller('PlayerCtrl', function($scope, $ionicPlatform, $cordovaMedia, $cordovaFile) {
  var directory = 'downloads';
  var filename = 'download.mp3';
  var media;  

  $scope.play = function() {
    return $ionicPlatform.ready(function() {})
    .then(function() {
      return $cordovaFile.checkFile(directory + '/' + filename);
    })
    .then(function(file) {
      media = $cordovaMedia.newMedia(file.nativeURL);
      $cordovaMedia.play(media);
    });
  };

  $scope.pause = function() {
    if (media)
    {
      $cordovaMedia.pause(media);
    }
  };

  $scope.rewind = function() {
    if (media)
    {
      $cordovaMedia.getCurrentPosition(media).then(function(res) {
      var mediaPosition = res - 15;
      var mediaInMilli = mediaPosition*1000;
      media.seekTo(mediaInMilli);
      });
    }
  };

  $scope.fastForward = function() {
    if (media)
    {
      $cordovaMedia.getCurrentPosition(media).then(function(res) {
      var mediaPosition = res + 15;
      var mediaInMilli = mediaPosition*1000;
      media.seekTo(mediaInMilli);
      });
    }
  };
}

The play functionality…

In order to play the file with $cordovaMedia, you first have to create a media object that $cordovaMedia can consume. We do this by calling $cordovaMedia.newMedia() and passing in the location of the file on the device with. The trick here is to get the URL to the file in the correct format. We found the easiest way to achieve this was to use $cordovaFile.checkFile() and read the nativeURL property of the result. Finally we can play the media object by calling $cordovaMedia.play(media).

Pause Functionality…

Nothing really worth instructing here. We just check that if a media object is present, call the $cordovaMedia.pause(media) function on it.

Rewind/Fast-Forward Functionality…

Finally, the rewind/fast-forward code – which was marginally tricky, but still relatively easy and straightforward.

To rewind/fast-forward we make use of 2 functions provided by $cordovaMedia; .getCurrentPosition() and .seekTo().
$cordovaMedia.getCurrentPosition(media) will (yep, you guessed it) return the current position of the media file, IN SECONDS.
When the current position is returned, we add/subtract 15 seconds from it (var mediaPosition = res + 15;) – this is our rewind/fast-forward.
Next we want to seekTo() our new position, to perform the rewind/fast-forward. Unfortunately, seekTo() is expecting our new media’s position to be calculated in milliseconds, so we have to convert this, by simply multiplying the media position by 1000 (var mediaInMilli = mediaPosition*1000;).
We now have our new media position in milliseconds, so we can just go ahead and call media.seekTo(mediaInMilli) – this will rewind/fast-forward our media file to that position, and it will still continue to play.

23
Dec

Ionic – In-app browser with ngCordova’s $cordovaInAppBrowser.

In the apps that we’ve built, there’s quite often been the need to open links to elsewhere on the internet. A simple href will open your phone’s web browser, and load the link in there. However, though this serves a purpose, it’s not an ideal user experience because you are taken totally out of the app.

To remedy this, ngCordova has a nice plugin that provides you with the ability to open a browser in-app. It’s not even remotely difficult to use, but hopefully people will find this useful, nevertheless.

We’re just going to cover the basics of opening a browser and navigating to a URL here, but the documentation does include some other usable functions, should you be that way inclined.

Let’s Start…

As with other posts, to make things simple we’ll start from the beginning and create a fresh Ionic Framework project through terminal/Git Bash (Linux/Windows)

ionic start IonicProject blank
cd IonicProject
ionic platform add android

Here you’ve created a fresh Ionic project, gone into the newly created Ionic app directory, and added the ability to build for the Android platform.

Next, follow the steps to install ngCordova.

Add the plugin…

In the same terminal/git bash add the ngCordova $cordovaInAppBrowser plugin:

cordova plugin add org.apache.cordova.inappbrowser

Onto the development. First we’ll create a button with an ng-click function, and pass through the URL we’d like to navigate to.

Let’s take a look at the HTML…

<button ng-click="watchYoutube("http://www.youtube.com")">Watch on Youtube!</p>

Next, our controller code…

.controller('InAppBrowserCtrl', function($scope, $cordovaInAppBrowser) {
  $scope.watchYoutube = function(url) {
    $cordovaInAppBrowser
     .open(url, '_blank')
     .then(function(event) {
       // success
     }, function(event) {
       // error
    });
  };
)};

All nice and simple stuff. Clicking the button passes the URL through to the $scope.watchYoutube() function, and the $cordovaInAppBrowser.open(url...) then opens the browser and navigates to that link. Obviously you can enter any success/error code if you see fit, but it’s not necessary.

Simple!

18
Dec

Ionic – Download progress bar for ngCordova’s $cordovaFile.

Part 1:

In an earlier post we described how we downloaded a file to our app, using $cordovaFile.

Users like to see how much of something has downloaded, so we decided that whilst the file was downloading, we’d display a download progress bar in the app.

Here’s how we did it…

First the HTML:

<!--Using the HTML5 progress element--> 
<progress ng-hide="download.progress==1;" max="1" value="{{download.progress}}"></progress>

As you can see, the progress bar has a ‘max’ value attribute of 1. The amount of progress displayed on the bar is calculated using the ‘value’ attribute. E.g. A ‘value’ of 0.3 is 30% of the ‘max’ value of 1, and so the progress bar is displayed at 30%.

Our value attribute is {{download.progress}}. Calculating the download progress of a downloading file isn’t difficult, as this is conveniently provided by the $cordovaFile.download() function.
The third argument of the .then() function provides the download’s progress information.

The controller’s download code…

exampleApp.controller('ExampleController', function($scope, $ionicPlatform, $cordovaFile) {
  var directory = 'downloads';
  var filename = 'download.mp3';

  $ionicPlatform.ready(function() {})
  .then(function() {
    return $cordovaFile.createDir(directory, false);
  })
  .then(function() {
    return $cordovaFile.createFile(directory + '/' + filename, false);
  })
  .then(function(newFile) {
    return $cordovaFile.downloadFile(url, newFile.nativeURL);
  })
  .then(function(result) {
    // Success!
  }, function(err) {
    // Error
  }, function (progress) {
    // constant progress updates
     download.progress = progress.loaded/progress.total;      
  });
});

In the progress callback (at the bottom of the code, above) we assign download.progress it’s value.

While this works great, there’s a problem…

Because the progress is calculated in the controller, if you navigate away from that page, and then return, the previous state is forgotten, and therefore so is the progress. To solve this, we need to move our download code into a Service, and $broadcast the download ‘event’ so that the download information is available throughout the whole app. When done properly, this will mean that the download (and it’s progress) will be accessible from any screen, and it won’t be forgotten when we change views.

Part 2:

Firstly we move the Controller’s download code into a Service, so that it can be accessed anywhere in the app.

Service Code…

exampleApp.factory('ExampleService', function($scope, $ionicPlatform, $cordovaFile, $interval, $rootScope) {
  var directory = 'downloads';
  var filename = 'download.mp3';
  var downloading = [];

  $ionicPlatform.ready(function() {})
  .then(function() {
    return $cordovaFile.createDir(directory, false);
  })
  .then(function() {
    return $cordovaFile.createFile(directory + '/' + filename, false);
  })
  .then(function(newFile) {
    downloading.push(podcast);
    return $cordovaFile.downloadFile(url, newFile.nativeURL);
  })
  .then(function(result) {
    // Success!
  }, function(err) {
    // Error
  }, function (progress) {
    // constant progress updates
     downloading.forEach(function(download){
       if(download.num == podcast.num){
          download.progress = progress.loaded/progress.total;
       }
     });           
  });
});

In addition to moving the download code into a Service (yes, a factory is a type of Service), you’ll notice that in the code we define an array called downloading, and when we download a podcast, we push it to that array. Then, in the progress callback we check forEach download in the downloading array (you may be downloading multiple podcasts), and determine whether the download is the same as the podcast you are currently downloading. We do this by comparing the .num. If they are the same, we assign the download progress to download.progress.

The next (and most important) thing we add to the Service, is a $broadcast event for the download, inside an $interval. The $broadcast will broadcast every download that currently belongs to the downloading[] array, to the $rootScope. Each ‘download’ is assigned to a ‘podcast’ variable (podcast:download).
We use $interval to define how often the download is broadcast. In our case, we set the interval to tick every 1 second.

The entire Service code…

exampleApp.factory('ExampleService', function($scope, $ionicPlatform, $cordovaFile, $interval, $rootScope) {
  var directory = 'downloads';
  var filename = 'download.mp3';
  var downloading = [];

  $interval(function(){
     downloading.forEach(download){
       $rootScope.$broadcast('download-progress', function(){
         podcast:download
       })
     }
  }, 1000);

  $ionicPlatform.ready(function() {})
  .then(function() {
    return $cordovaFile.createDir(directory, false);
  })
  .then(function() {
    return $cordovaFile.createFile(directory + '/' + filename, false);
  })
  .then(function(newFile) {
    downloading.push(podcast);
    return $cordovaFile.downloadFile(url, newFile.nativeURL);
  })
  .then(function(result) {
    // Success!
  }, function(err) {
    // Error
  }, function (progress) {
    // constant progress updates
     downloading.forEach(function(download){
       if(download.num == podcast.num){
          download.progress = progress.loaded/progress.total;
       }
     });           
  });
});

So now, if there is a podcast downloading, the information about that downloading podcast will be broadcast to the whole app, every 1 second. The next thing we need to do, is access this information in the Controller for whatever view we happen to be displaying the progress bar.

To do this, we listen for the $broadcast event in the Controller, by using $scope.$on('download-progress'). Within $on(), we loop through the array ($scope.downloads) of all the downloads we have on the app, and find the download that matches the currently downloading podcast that is being broadcast. We get an array of all the downloads on the app by calling ExampleService.allDownloads() – we then assign this to $scope.downloads. This array includes both partially and fully downloaded podcasts. When we find a match, we copy the information of the downloading podcast that is being broadcasted, to the download in the $scope.downloads array. This means that every second the download will have updated information about the broadcasted download, including the progress!

Controller code…

 .controller('DownloadsCtrl', function($scope, $ionicPlatform, ExampleService) {

   $scope.downloads = ExampleService.allDownloads();
 
   $scope.$on('download-progress', function(event, args) {
     $scope.downloads.forEach(function(d) {
       if (d.num == args.podcast.num) {
         angular.copy(args.podcast, d);
       }
     });
   });

As you can see above, we loop through every (partial or full) download in $scope.downloads, and compare each download with the downloading podcast that is being broadcast. args contains all information about the downloading podcast. If the two match, then we copy the ALL of information of the broadcasted download to the download in the $scope.downloads array. Since the progress is part of that information, this gets updated every second as well, giving us a fully functional, and reliable, progress bar.

16
Dec

Ionic – Adding an App Icon and Splash Screen

Adding an app icon and a splash screen are generally quite essential to the finishing of an app. Up until recently, because of the various different device screen sizes and resolutions, this caused a not insubstantial amount of trauma to us developers.

With Ionic’s CLI release 1.2.14, this process has been made as simple and seamless as most other things contained in the Ionic framework.

Here’s the blog post from Ionic detailing the changes they’ve made to this process: http://ionicframework.com/blog/automating-icons-and-splash-screens/

Unfortunately, despite the process now being simple, the Ionic blog post doesn’t actually take you through the whole process, and it took us a little while to figure out exactly how to do it. Hopefully the following should be instructive enough for any devs who’ve run into the same problems that we did with this…

Ok, firstly:

1. For the app icon, you need an icon with size: 192px*192px.
2. For the splash screen, you need an overall size of at least: 2208px*2208px, and any centre graphic you may have should fit within a 1200px*1200px centre square.

Next:

1. Create a folder named ‘resources’ at the top level of your project, e.g: ‘projectName/resources’.
2. Place both the app icon and the splash screen in the ‘resources’ folder.

If necessary, update the Ionic CLI by running:

npm install -g ionic

Then run:

ionic resources

Running ‘ionic resources’ sends your splash screen and app icon to Ionic’s image resizing and cropping server, which creates the various different image sizes necessary for compatibility with all devices.

If you now build your app, you’ll notice that the app icon is present. The splash screen, however, is not – though we only tested this on an Android device. To get the splash screen to display (on Android, at least), you need add the following preferences to your project’s config.xml file:

 <preference name="SplashScreen" value="screen"/>
 <preference name="SplashScreenDelay" value="10000"/>

The ‘SplashScreenDelay’ preference is optional.

If you now build the app both the app icon and the splash screen should be present.

14
Dec

Ionic – Opening a downloaded file with ngCordova’s $cordovaFile.

After downloading our files to our directory, logically, the next step was to display those files in the app.

The documentation on the ngCordova site for listing the contents of a directory, is beyond scant, as there is no mention of any function that would allow you to do it.

After going into the source code, we found a function that would nicely do the trick…Continue Reading..

12
Dec

Ionic – Downloading a file with ngCordova’s $cordovaFile.

Recently we decided to build an Android podcast app using the Ionic framework and ngCordova. ngCordova is an angularJs wrapper for Apache Cordova plugins, which in theory, should make them nice and easy to use within your angular/cordova apps. Here’s the link to ngCordova: http://ngcordova.com/docs/

Unfortunately the documentation is rather scant, and solutions to most of the issues we were encountering were absent from Google, so we thought we’d document the process here. We found that many people were having similar problems to us (on the Ionic forums/StackOverflow), so hopefully this should help you guys out.Continue Reading..

10
Dec

Ionic – Black screen on iOS and Android device when using Live Reload

As well as running ‘ionic serve’ to test your app in a browser, it’s possible to test out your app on a device by using ‘ionic run’. This works fine, but it’d be nice to have a live reload on your device for whenever you make a change to your code. This also works fine until you run into an error, at which point you’ll be hankering for some console or server errors to help you out. Ordinarily see these in your browser, when using ‘ionic serve’.

Fortunately, in order to experience live reload, and to see these console/server errors in your terminal/Git Bash, Ionic have kindly provided us with these features. Continue Reading..

11
Apr

Deleting all files which do not contain @2x in Windows

[codesyntax lang="bash"]
for /f "eol=: delims=" %F in ('dir /b /a-d * ^| find /v /i "@2x"') do del "%F"
[/codesyntax] From http://stackoverflow.com/questions/10788075/dos-batch-for-loop-to-delete-files-not-containing-a-string