Phonegap Build + Ionic: Plugin 'Device' not found, or is not a CDVPlugin

All,

I have decided to try using ionic and phonegap build. Everything was going well until I tried using plugins. As soon as I attempt to call a plugin I get

Plugin 'Device' not found, or is not a CDVPlugin

In the weinre console, I get

deviceready has not fired after 5 seconds. Channel not fired: onCordovaInfoReady

I can see on Phonegap Build that the plugins are recognized, and if I download the ipa file and expand the contents, I can see everything is there. I am suspecting this is an ionic issue as I was working with Ismael from Phonegap (super responsive and helpful, BTW). He sent me a phonegap sample, and based on his config settings and code, I am running the same and still having problems. If anyone can help me out I would greatly appreciate it.

What I have done

  1. sudo npm install ionic -g
  2. sudo npm install phonegap -g
  3. sudo npm install cordova -g
  4. sudo ionic start xxxx
  5. sudo ionic platform add ios
  6. cd xxxx
  7. cd www
  8. phonegap remote build ios

Environment

  • OSX 10.9.2
  • Device iPhone 4s running iOS 7.1
  • PhoneGap Build project version 3.3.0
  • ionic-v1.0.0-beta.1
  • phonegap CLI 3.4.0-0.19.8
  • cordova CLI 3.4.0-0.1.3

config.xml

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.xxxx.xxxx" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:gap="http://phonegap.com/ns/1.0">
    <name>xxxxxxxx</name>
    <description>
        xxxx
    </description>
    <author email="xxxx@gmail.com">
        xxxx
    </author>
    <content src="index.html" />
    <access origin="*" />
    <preference name="phonegap-version" value="3.3.0" />
    <preference name="fullscreen" value="true" />
    <preference name="webviewbounce" value="false" />
    <preference name="UIWebViewBounce" value="false" />
    <preference name="DisallowOverscroll" value="true" />
    <gap:plugin name="org.apache.cordova.device" />
    <gap:plugin name="org.apache.cordova.media" />
    <gap:plugin name="org.apache.cordova.media-capture" />
    <gap:plugin name="org.apache.cordova.file" />
</widget>

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title>Ionic Seed App</title>

    <!-- ionic css -->
    <link href="lib/css/ionic.css" rel="stylesheet">

    <!-- your app's css -->
    <link href="css/app.css" rel="stylesheet">

    <!-- ionic/angularjs scripts -->
    <script src="lib/js/ionic.bundle.js"></script>

    <script src="lib/js/moment.min.js"></script>

    <!-- cordova script (this will be a 404 during development) -->
    <script src="cordova.js"></script>
    <!-- your app's script -->
    <script src="js/app.js"></script>
    <script src="js/services.js"></script>
    <script src="js/controllers.js"></script>

  </head>

  <body ng-app="xxxx" animation="slide-left-right-ios7">

    <!--
      The nav bar that will be updated as we navigate between views
      Additional attributes set its look, ion-nav-bar animation and icons
      Icons provided by Ionicons: http://ionicons.com/
    -->
    <ion-nav-bar type="bar-positive"
             animation="nav-title-slide-ios7"
             back-button-type="button-icon button-clear"
             back-button-icon="ion-ios7-arrow-back"></ion-nav-bar>

    <!--
      The views will be rendered in the <ion-nav-view> directive below
      Templates are in the /templates folder (but you could also
      have templates inline in this html file if you'd like).
    -->
    <ion-nav-view></ion-nav-view>

  </body>
</html>    

app.js

var app = angular.module('xxxx', ['ionic', 'xxxx.services', 'xxxx.controllers']);

app.value('globals', {
    'ACSKey': 'xyz',
    'session_id': '',
    'logged_in_userId': ''
});

app.config(function($stateProvider, $urlRouterProvider) {

    // Ionic uses AngularUI Router which uses the concept of states
    // Learn more here: https://github.com/angular-ui/ui-router
    // Set up the various states which the app can be in.
    // Each state's controller can be found in controllers.js
    $stateProvider

    .state('login', {
        url: "/login",
        templateUrl: "templates/login.html",
        controller: 'LoginCtrl'
    })

    .state('register', {
        url: "/register",
        templateUrl: "templates/register.html",
        controller: 'RegisterCtrl'
    })

    // setup an abstract state for the tabs directive
    .state('tab', {
        url: "/tab",
        abstract: true,
        templateUrl: "templates/tabs.html"
    })

    .state('main', {
        url: "/main",
        templateUrl: "templates/main.html",
        controller: "MainCtrl"
    })

    .state('premise', {
        url: "/premise",
        templateUrl: "templates/premise.html",
        controller: "PremiseOfTheDayCtrl"
    })

    .state('friends', {
        url: "/friends",
        templateUrl: "templates/friends.html",
        controller: "FriendsCtrl"
    })

    .state('tab.about', {
        url: '/about',
        views: {
            'about-tab': {
                templateUrl: 'templates/about.html'
            }
        }
    });

    // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/login');

});

app.run(function($rootScope, $location, $ionicPlatform) {

    document.addEventListener("deviceready", onDeviceReady, false);

    function onDeviceReady() {
        alert('deviceready');
    }

    // $ionicPlatform.ready(function() {
    //     // Initialize plugin here
    //     console.log('ionicPlatform.ready');
    // });

    var _session_id = window.localStorage.getItem("_session_id");
    var logged_in_userId = window.localStorage.getItem("logged_in_userId");

    if (_session_id === null || _session_id === '') {
        // no logged user, we should be going to #login
        if (next.templateUrl === "templates/login.html") {
            // already going to #login, no redirect needed
        } else {
            // not going to #login, we should redirect now
            $location.path("/login");
        }
    } else {
        $location.path("/main");
    }

});

I've spent considerable time testing plugins on Phonegap Build, and found that the plugins would work for Phonegap 2.9, but break for versions 3.1 and 3.3 on Phonegap Build.

There was no workaround and I've resorted to building locally for iOS and Android.

The Phonegap platform has some issues with plugins even when building locally, and this script fixed it for me on the iOS side:

#!/bin/bash
echo "Killing xcode..."
killall Xcode

rm -rf platforms/ios
rm plugins/ios.json
cordova platform add ios
cordova build ios
node .cordova/hooks/after_build/copy_icons_screens.js 
## Above isn't being caught by the hook during iOS for some reason. Hook is caught by Android build
open platforms/ios/*.xcodeproj

On the Android side, be sure to clean the CordovaLib project THEN clean your AppName project - the order matters.

The copy icons and install plugins hooks here are also key:
http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/

Apparently waiting a few weeks and trying again allowed Phonegap Build to catch up. When I tried again, the plugins were recognized and my audio record dialog was displayed...