Showing posts with label Cordova. Show all posts
Showing posts with label Cordova. Show all posts

Sunday, January 8, 2017

Cordova FacebookConnect Plugin Not Working in iOS 9 and iOS 10

Recently in one of my Cordova project we have Connect with Facebook functionality where I have faced certain issues to make it working in iOS 9 and iOS 10 so in this blog I am going to explain those issues and how to resolve it.

1) Which Plugin to Use

There are two plugins if you search for Cordova Facebook Plugin. Following are two links.

GitHub - Wizcorp/phonegap-facebook-plugin

GitHub - jeduan/cordova-plugin-facebook4

The first plugin is older one and it works good till iOS 7 and iOS 8 and cordova 4.0 iOS but it does not work for iOS 10 and Cordova 6.0. If you use that plugin you will usually get issues like you will not be redirected to Facebook in mobile safari or after authentication, it come back to your app and nothing happen.

So my recommendation is to go for second Plugin, which works fine with latest cordova and iOS 9 and iOS 10.

2) After authentication, it come back to your app and nothing happen.

This issue is observed in iOS 9 and iOS 10 for both the plugins. That's because of LSApplicationQueriesSchemes introduced in iOS 9 on words.

There are two URL-related methods available to apps on iOS that are effected: canOpenURL and openURL. These are not new methods and the methods themselves are not changing. As you might expect from the names, “canOpenURL” returns a yes or no answer after checking if there is any apps installed on the device that know how to handle a given URL. “openURL” is used to actually launch the URL, which will typically leave the app and open the URL in another app.

Up until iOS 9, apps have been able to call these methods on any arbitrary URLs. Starting on iOS 9, apps will have to declare what URL schemes they would like to be able to check for and open in the configuration files of the app as it is submitted to Apple. This is essentially a whitelist that can only be changed or added to by submitting an update to Apple.

So you have to WhiteList all the Facebook App Schemes in your info.plist file. Following are schemes you have to add.

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>fbapi</string>
    <string>fbapi20130214</string>
    <string>fbapi20130410</string>
    <string>fbapi20130702</string>
    <string>fbapi20131010</string>
    <string>fbapi20131219</string>  
    <string>fbapi20140410</string>
    <string>fbapi20140116</string>
    <string>fbapi20150313</string>
    <string>fbapi20150629</string>
    <string>fbapi20160328</string>
    <string>fbauth</string>
    <string>fbauth2</string>
    <string>fb-messenger-api20140430</string>
</array>

3) There was an error making the graph call

I spent almost an hour to solve this issue. Everything was configured but when I try to make graph API call to get basic profile, it fails every time and the reason behind this was in graph api call IO have space after each field.

facebookConnectPlugin.api("me/?fields=id, first_name, last_name, email",["public_profile"],
function (result) {
},
function (error) {
});

As you can see above there was a space after each field. So to make it working. Remove that space. It does not affect in Android but it does not work in iOS.

facebookConnectPlugin.api("me/?fields=id,first_name,last_name,email",["public_profile"],
function (result) {
},
function (error) {
});

Friday, December 23, 2016

Cordova Text To Speech Plugin

Recently in one of our project we added Cordova Text To Speech using following plugin.

https://github.com/PluginCordova/cordova-plugin-tts

But during the development I faced certain issues so in this blog I am going to explain it.

1) It does not work in first attempt.

This plugin depends on Android Text to Speech class

https://developer.android.com/reference/android/speech/tts/TextToSpeech.html

And for that you need sample voice in the phone. If you are using this feature first time then first it will download sample voice from the android server. So let it get downloaded and then try it again.

2) Maximum Character Limit

There is limit of 32, 768 characters. If your string is bigger than this, it won't work. In this case split your text to smaller chunks and play it one by one like playlist.

3) It does not stop after start playing.

With the above plugin it does not stop playing. The reason is stop method is not implemented at all in this plugin.

So you have to make two changes for it.

First open tts.js file in plugins folder and exports.stop = function ()

There check for the following line.

cordova
.exec(function () {
if (promise) {
promise.resolve();
}
}, function (reason) {
}, 'TTS', 'stop');

Here the third param options is not passed so it will give JavaScript error. Change above code to.

cordova
.exec(function () {
if (promise) {
promise.resolve();
}
}, function (reason) {
}, 'TTS', 'stop',[]);

Now go to TTS.java file in src folder of your android project and look for the following method.

public boolean execute(String action, JSONArray args, CallbackContext callbackContext)

It does not have stop implementation.

Remove code of the function and use following code.

if (action.equals("speak")) {
speak(args, callbackContext);
} else if (action.equals("stop")){
Log.v("stop","stop speaking");
tts.stop();
}else{
return false;
}
return true;

That's it and it should work now.

Monday, December 19, 2016

Cordova Upload PDF File

Recently in my project I created Hybrid application using Cordova. There was a requirement where we allow user to choose PDF file or any type of file and upload it to server.

So here are two parts, first let user choose file from SD card or phone memory or from iCloud drive on iOS.

Second part is to upload file to server with progress and store it and get it's path back in case if you want to show it some where. I will show example code on server side.

So lets first check the first part. For this we need following plugins. Please install it first.

https://github.com/jcesarmobile/FilePicker-Phonegap-iOS-Plugin This is specifically for iOS
https://github.com/don/cordova-filechooser This is specifically for Android
https://github.com/apache/cordova-plugin-file
https://github.com/apache/cordova-plugin-file-transfer

The file picker iOS plugin which I have mentioned here will not work for local photos and videos stored in camera roll. For this you have to make certain changes in the plugin. I have mentioned this in my previous blog. Please read it here.

http://davehiren.blogspot.com/2016/12/filepicker-cordova-ios-plugin-get-files.html

Now first lets invoke the plugin.

Android Example

fileChooser.open(function(obj) {
        var filePath = obj.path;
});

iOS Example

FilePicker.pickFile(function(obj) {
        obj = obj[0];
         var filePath = obj.path;
});

In both of this case we will get absolute path of files like

/path/of/file/filename.extension

Now we will have our logic to upload file to server using Cordova File Transfer plugin.

First of all we will get extension of file and keep it separate also we will have file name extracted from the path to send it to server.

var fileType = filePath.substring(obj.path.lastIndexOf('.'));

var options = new FileUploadOptions();
options.fileKey = "file";
options.fileName = filePath.substr(this.evidencePath.lastIndexOf('/') + 1);
options.mimeType = "text/plain";

var params = {};
params.fileType = type;
options.params = params;

var ft = new FileTransfer();

ft.onprogress = function(progressEvent) {
if (progressEvent.lengthComputable) {
//in case you want to show progress bar , your code goes here.
} else {
//loadingStatus.increment();
}
};

var win = function (r) {
        //success alert or your logic after successful upload
};

var fail = function (error) {
        //failure alert or your logic after successful upload
};

ft.upload(fileURI, encodeURI("http://pathtoyourserver"), win, fail, options);

So this was on JavaScript side. Now lets see on server side. I used PHP on server side so I will give you example of that. If you are using something else on server side, please implement your own logic.

$file_type = $_POST['fileType'];
$fileName = time()."_".$file_type;
move_uploaded_file($_FILES["file"]["tmp_name"], "/your/server/path/".$fileName);
return json_encode(array('success'=>true, 'server_path'=>'http://yourserverpath.com'.$fileName));

With this logic you can upload any type of file from your cordova app.

Friday, December 16, 2016

Objective C - Play Video From Application Temp Folder

Recently in one of my iOS project , there was requirement to play Video stored in temporary folder of Application data. After some hours of struggle I managed to get it working.

So the problem I was facing is I have absolute URL of the video that I was trying to play in MPMoviePlayerController but it was not working as the player was displayed for couple of seconds and it's dismissed automatically and there was a black screen.

So after sometime I found out that MPMoviePlayerController is deprecated, instead of it we shall use AVPlayer and that too was not working if I give absolute path to initialize a player. So first of all I just extracted file name fro the absolute path will following code.

NSRange range = [filePath rangeOfString:@"/" options:NSBackwardsSearch];
NSUInteger index = range.location;      
filename = [filePath substringFromIndex:index+1];

Now we will initialize player. Please note you have to import AVKIt first in your header file.

#import <AVKit/AVKit.h>

Now initialize player.

NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:outputPath]];
AVPlayer *_avPlayer = [[AVPlayer alloc]initWithPlayerItem:[[AVPlayerItem alloc]initWithAsset:asset]];

movieLayer = [AVPlayerLayer playerLayerWithPlayer:_avPlayer];
movieLayer.frame = self.view.bounds;
[self.view.layer addSublayer:movieLayer];

So player will be added as sublayer on the view so we have to dismiss it when video finished playing.

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(videoDidFinish:)
name:AVPlayerItemDidPlayToEndTimeNotification
  object:[_avPlayer currentItem]];

And add callback function to remove player layer.

- (void)videoDidFinish:(id)notification
{
    NSLog(@"finsihed");
    [movieLayer removeFromSuperlayer];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


That's it. Hope this will help you.

FilePicker Cordova iOS Plugin - Get Files From Photos

Hello,

Recently in one of my iOS project we have requirement to let user browse and select files. So we needed FilePicker plugin for iOS. It should also allow user to browse through document providers like iCloud drive, Dropbox or Google drive.


So after searching for the plugin I found following plugin which works fine for iCloud drive.


https://github.com/jcesarmobile/FilePicker-Phonegap-iOS-Plugin


I would like to thank developer of above plugin as I just added more code to it to fulfill my requirement.

But my other requirements were not fulfilled to pick up photos and videos from saved photos album so I made some changes in this plugin. Here in this blog I will explain how to do this.

First of all install above plugin through command line and open your project in Xcode and open file

Plugins ==> FilePicker.h and Plugins ==> FilePicker.m

This plugin shows pop over menu with all available document providers so first we have to add option to browse photos and videos.

Open FilePicker.h file and add following import statement.

#import

And add following delegates.

@interface FilePicker : CDVPlugin

Now Open FilePicker.m file and find displayDocumentPicker and add following code to it.

[importMenu addOptionWithTitle:@"Photos & Videos" image:nil order:UIDocumentMenuOrderFirst handler:^{
        
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
imagePickerController.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:imagePickerController.sourceType];
imagePickerController.allowsEditing = NO;
imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh;
imagePickerController.delegate = self;
[self.viewController presentViewController:imagePickerController animated:YES completion:nil];

}];


This will add menu and we set delegate to self so now we have to callback functions. Add following function in the file.

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
    if ([mediaType isEqualToString:@"public.image"]){
        NSData *imageData = UIImagePNGRepresentation((UIImage*) [info objectForKey:UIImagePickerControllerOriginalImage]);
        NSString* size = [NSString stringWithFormat:@"%li",  (unsigned long)[imageData length]];
        NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
        NSNumber *timeStampObj = [NSNumber numberWithInteger:timeStamp];
        NSString* fileName = [timeStampObj stringValue];
        
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        NSString *imagePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png",fileName]];
        if (![imageData writeToFile:imagePath atomically:NO])
        {
            //send failure response;
            self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Failed to cache image data to disk"];
            [self.pluginResult setKeepCallbackAsBool:NO];
            [self.commandDelegate sendPluginResult:self.pluginResult callbackId:self.command.callbackId];
        }
        else
        {
            NSArray *arr = @[
                             @{@"path": imagePath, @"size": size}
                             ];
            
            self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:arr];
            [self.pluginResult setKeepCallbackAsBool:NO];
            [self.commandDelegate sendPluginResult:self.pluginResult callbackId:self.command.callbackId];
        }
    }
    else if ([mediaType isEqualToString:@"public.movie"]){
        NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
        NSString* path = [[videoURL absoluteString] substringFromIndex:7];
        NSData *data = [NSData dataWithContentsOfURL:videoURL];
        NSString* size = [NSString stringWithFormat:@"%li",  (unsigned long)[data length]];
        NSArray *arr = @[
                         @{@"path": path, @"size": size}
                         ];
        
        self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:arr];
        [self.pluginResult setKeepCallbackAsBool:NO];
        [self.commandDelegate sendPluginResult:self.pluginResult callbackId:self.command.callbackId];
    }
    [picker dismissViewControllerAnimated:YES completion:NULL];
}

So as you can see in above code we are checking if picked media is image, then first we have to move application temp storage as iOS does not allow you to access assets directly from photos so we are making a copy with following code.

NSData *imageData = UIImagePNGRepresentation((UIImage*) [info objectForKey:UIImagePickerControllerOriginalImage]);
NSString* size = [NSString stringWithFormat:@"%li",  (unsigned long)[imageData length]];
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
NSNumber *timeStampObj = [NSNumber numberWithInteger:timeStamp];
NSString* fileName = [timeStampObj stringValue];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *imagePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png",fileName]];
if (![imageData writeToFile:imagePath atomically:NO])
{
}
else
{
}


And for the videos we are sharing absolute URL to result callback. Also the plugin result is now array with path and size attribute. So we have to change the code of plugin to send same result for other document providers. Find out following function in FilePicker.m file.

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url

And replace it with following function.

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url {
    
    [url startAccessingSecurityScopedResource];
    __block NSData *pdfData = nil;
    
    NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
    __block NSError *error;
    [coordinator coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
        pdfData = [NSData dataWithContentsOfURL:newURL];
        NSString* size = [NSString stringWithFormat:@"%li",  (unsigned long)[pdfData length]];
        NSArray *arr = @[
                         @{@"path": [url path], @"size": size}
                         ];
        
        self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:arr];
        [self.pluginResult setKeepCallbackAsBool:NO];
        [self.commandDelegate sendPluginResult:self.pluginResult callbackId:self.command.callbackId];
    }];
    [url stopAccessingSecurityScopedResource];
    
}

So in JavaScript, following code should work.

FilePicker.pickFile(function(obj) {
alert(obj[0].path);
alert(obj[0].size);
}

Hope this helps you.

Saturday, December 10, 2016

Cordova Application Hanging During Startup on iOS 10

Hello,

If you have any cordova application in iTunes, you may have faced this issue since launch of iOS 10,. Either your app hangs at Start up or it will hang in case when there is a use of any plugin, like camera or location or any other native features. 

This is because of content security policy. iOS 10 needs content security policy where you have to mention what types of content you will allow to load.

As you have notice cordova plugins are invoked gap:// and in iOS 10 it's not allowed by default so you have to mention this in content security policy. 

Add following line in head section of your index.html file.

<meta http-equiv="Content-Security-Policy" content="media-src *; img-src * data:; font-src * data:; default-src  * gap:; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">


As you can see we have added gap: in allowed content src along with other source, now your app will work normally in iOS 10.

Hope this helps you.

Wednesday, December 7, 2016

Top 3 Issues We Face With Sencha Touch Android Native App

As we all know using Sencha Touch and Cordova/Phonegap we can build native android application. Here in this blog I will mentioned top 3 issues we have to face while using Sencha Touch with Cordova and build native application. All three issues are related to Android Back Button event.



1) Android Back Button Press



Most of the android users are very much used to with android phone back button and Sencha Touch framework is very much user friendly with iOS. With Sencha Touch app on android, if you press back button of phone it exits the app instead of going back to previous screen. Because this back event is not properly handled by Sencha Touch framework. To solve this you have to bind Android back key press event with cordova and add your logic to go back to previous screen. I have mentioned this in my blog.

Please check Cordova Android Back Button Event

2) Hide Sencha Touch Pickers on Back Key Press



Ae we know sencha touch select field, date picker, action sheet shows picker to choose items from. Normally in android this types of pickers are dismissed when user press android back button. Button in case of sencha touch this types of pickers are nothing but floating panels and that does not hide on back key event. To solve this you have to bind Android back key press event with cordova and add your logic to go back to previous screen. I have mentioned this in my blog.

Please check Sencha Touch Hide Pickers On Android Back Key Press

3) Hide Sencha Touch Alert Box on Back Key Press



In Sencha Touch we have alert and confirm boxes. Normally in android this types of dialogs are dismissed when user press android back button. Button in case of sencha touch this types of alerts are nothing but floating panels and that does not hide on back key event. To solve this you have to bind Android back key press event with cordova and add your logic to go back to previous screen. I have mentioned this in my blog.

Please check Sencha Touch Hide Alert Box on Android Back Key Press

Saturday, October 15, 2016

Sencha Touch Hide Pickers on Android Back Key Press

Hello,

I recently published an article about hiding sencha touch Message box on press of back button in android. You can read it here Sencha Touch Hide Alert Box on Android Back Button Press

This post is something similar to it. Recently we created an android application with Sencha Touch and Cordova for client. After testing, client came up with requirement that if any picker is open for example calendar picker or select field picker etc. It should be closed on press of back button in android. So after efforts of half an hour, I found a solution. 

In Sencha Touch pickers are basically floating panels. So what we have to do it get all the floating panels and see if it's hidden or not. If not hidden then hide it.

First of all you have to bind back button event of android using cordova. Please check this cordova document on how to do this here.

Once you add backbutton listener, add following code.

var floatingComponents = Ext.query('.x-floating');
for(var i=0;i
var component = Ext.getCmp(floatingComponents[i].id);
if(component){
if(component.isHidden() == false){
component.hide();
return;
}
}
}

As you can see in above code, first we are finding all the floating component using Ext.query and class x-flaoting

Then we loop through it and find component using id of floating component and check if component is not hidden then hide it and return from there. 

Hope this helps you.

Sunday, September 25, 2016

Sencha Touch Hide Alert Box on Android Back Button Press

Hello,

Recently in one my project our client gave use very strange requirement. We have used Sencha Touch to create application and used Cordova to create native app.

As we all know with Sencha Touch we use Ext.Msg.alert() to show user alert. This alert has OK button. When user tap on that alert goes away.

However in our case client asked us to hide this alert if its on and user presses virtual or physical hardware button of android phone. So after hearing this requirement first of I was confused and was not sure how to implement it. But after looking at docs and source code of Ext.Msg class solution was very easy so on this blog I am going to explain how to do this.

First of all we have to bind back button key event. Add following code to your app.js file.

if (Ext.os.is('Android')) {
            document.addEventListener("backbutton", Ext.bind(onBackKeyDown, this), false);
            function onBackKeyDown(e) {
            }
}

Now as we all know Ext.Msg is singleton class and the alerts and confirm boxes are nothing but a floating panels. So we can just simply check if it's hidden or not and hide it if required. Check the following code.


if (Ext.os.is('Android')) {
            document.addEventListener("backbutton", Ext.bind(onBackKeyDown, this), false);
            function onBackKeyDown(e) {
                     if(Ext.Msg.isHidden() !=  null){
                             if(Ext.Msg.isHidden() == false){
                                       Ext.Msg.hide();
                             }
                     }
                     else{
                             var comp = Ext.getCmp("ext-sheet-1");
                             if(comp){
                                 comp.hide();
                            }
                     }
                     e.preventDefault();
            }
}


As you can see in above code. First we are checking Ext.Msg.isHidden() !=  null, this is to check if there is no instance of alert is created yet, there is no meaning of hiding it.

Then we check if Ext.Msg.isHidden() == false then just hide it or else don't do anything. That's it. Hope this helps you.

Cordova Applicaiton Select Any File From SD Card and Upload to Server

Hello,

Recently in one of my project we were building android application with Cordova. There was a requirement where user can choose any files from SD card and upload it to server.

So in this blog I am going to explain how to do this.

Our challenges was user can pick any file like audio, video, image, pdf etc. So we have to properly save it on server with proper extension.

First of all you will need two plugins.

1) Cordova File Transfer Plugin

You can install it via following command.

cordova plugin add cordova-plugin-file-transfer

2) File Chooser Plugin

You can check this plugin here and can install it via following command.

cordova plugin add http://github.com/don/cordova-filechooser.git

Now once you install file choose plugin you can use following command to open file selector in android.

fileChooser.open(function(uri) {
    alert(uri);
});

Now the problem with this file chooser plugin is that it gives content path like this.

content://media/images/4

However this path can not be used with File Transfer Plugin as it needs absolute file URI like this.

file:///sdcard/0/downloads/myPDF.pdf

So we have to edit this file chooser plugin little bit. Go to your android project and open FileChooser.java file from the com.megster.cordova package and check the following function.


public void onActivityResult(int requestCode, int resultCode, Intent data) {

}

See the following line of code in this function.

Uri uri = data.getData();

 callback.success(uri.getPath());

So form here it returns the content path.

We have to convert that content path to absolute path. So add following functions in FileChooser.java


public static String getPath(final Context context, final Uri uri) {

        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @param selection (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri, String selection,
            String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }


    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());

    }

Now update onActivityResult function as follow.

callback.success(getPath(cordova.getActivity(), uri));

It will send you correct absolute URI.  Now in your JavaScript.

var filePath = '';
var fileType = '';

fileChooser.open(function(obj) {
            filePath = 'file://'+obj;
            fileType= obj.substring(uri.lastIndexOf('.'));
        });

As you see above we are storing file path and file extension in two variables. Now following is the code to upload your file to server.

        var win = function (r) {
                alert('file uploaded');
        }

        var fail = function (error) {
         }

        var options = new FileUploadOptions();
        options.fileKey = "file";
        options.fileName = filePath.substr(this.evidencePath.lastIndexOf('/') + 1);
        options.mimeType = "text/plain";

        var params = {};
        params.fileType = fileType;
        options.params = params;

        var ft = new FileTransfer();
        ft.upload(filePath, encodeURI("http://yourserverpath"), win, fail, options);

On the server side your PHP code should be as follow.

$file_type = $_POST['fileType'];
$fileName = time()."_".$file_type;
move_uploaded_file($_FILES["file"]["tmp_name"], "/yourserverpath/".$fileName);
return json_encode(array('success'=>true,'index'=>$index, 'server_path'=>"http://serverpath".$fileName));

That's it, hope this helps you.

Saturday, September 3, 2016

PDF.JS Not Working - Sencha Touch 2 PDF Panel Not Working After Build

Hello,

Recently in one of my project we were using following Sencha Touch 2 PDF panel to display PDF in native application build with Cordova.

I faced a strange issue here. App was working fine without any build but when I generate a production build and build the native android app PDF panel stopped working and throws following error.

Unhandled rejection: TypeError: undefined is not an object (evaluating 'viewBox')

I tried to find out a solution for sometime but could not get any. I was using following PDF panel.

https://github.com/SunboX/st2_pdf_panel

Suddenly I saw that there is no update since last three years and that could be probably be the reason. So what I did is downloaded latest PDF.js and built the minified files and used it and bingo it worked. So here I am going to explain the steps.

1) First of clone latest PDF.js source from it's git.

$ git clone git://github.com/mozilla/pdf.js.git

2) Now go to the folder.

$ cd pdf.js

3) Install Node.js if its not installed.

$ npm install -g gulp-cli

4) After node.js is installed, install all the dependencies.

$ npm install

5) Build PDF.js files.

$ gulp generic

it will generate minified files in directory build/generic/build/

Copy pdf.js and pdf.worker.js files from this directory to your lib folder and that's it now PDF will be displayed even in testing or production build.

Saturday, August 13, 2016

Cordova Camera Plugin Select Video Returns null FILE URI

Hello,

Recently in one of my project we have a requirement to select video from Gallery and import it to Cordova application.

For that I used following code.

        navigator.camera.getPicture(this.onVideoSuccess, function(){
            console.log('Error in Video Processing);
        }, {
            destinationType: Camera.DestinationType.FILE_URI,
            sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY,
            mediaType:Camera.MediaType.VIDEO,
            limit: 1
        });

As you can see in above code we are selecting media type to video. Following is our callback function.

    onVideoSuccess: function(videoURI){
        console.log(videoURI);
    }

Now the problem was it was always returns null URI for any video I select. So I decided to dig into plugin code and found issue in CameraLauncher.java. Following is the function.


private void processResultFromGallery(int destType, Intent intent) {
 Uri uri = intent.getData();
        if (uri == null) {
            if (croppedUri != null) {
                uri = croppedUri;
            } else {
                this.failPicture("null data from photo library");
                return;
            }
        }
        .........
        .........
Here we have issue in following line.

        String fileLocation = FileHelper.getRealPath(uri, this.cordova);

This always returns null, however we were getting URI. So I added following code. 

if (this.mediaType != PICTURE) {
         if (fileLocation == null) {
         Cursor cursor = null;
          try { 
            String[] proj = { MediaStore.Images.Media.DATA };
            cursor = this.webView.getContext().getContentResolver().query(uri,  proj, null, null, null);
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            fileLocation = cursor.getString(column_index);
          } finally {
            if (cursor != null) {
              cursor.close();
            }
          }
         }
            this.callbackContext.success(fileLocation);
        }

and it worked.  Now it returned correct URI.

Hope this helps you.

Cordova / Sencha Touch Application Import and Show Image form Native Phone Gallery

Hello,

Recently in one of my cordova application we have requirement to select images from Phone gallery and display it in list view.  For that we have to allow use to select multiple images from gallery. Here in this blog I am going to explain how to do this.

First of all we will need following plugin installed.

Cordova Image Picture.

https://github.com/wymsee/cordova-imagePicker

If you install this plugin with following command, some how it does not work.

cordova plugin add cordova-plugin-image-picker

So instead of installing it from the command install it with direct git URL.

cordova plugin add "https://github.com/wymsee/cordova-imagePicker"

Along with above plugins, we will also need Cordova File Plugin and Cordova File Transfer Plugin.

Install it with following commands.

cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-file-transfer

Now first of all we have to open image gallery with this image picker plugin to choose images.

Add following code.

window.imagePicker.getPictures(
    function(results) {
        for (var i = 0; i < results.length; i++) {
            console.log('Image URI: ' + results[i]);
        }
    }, function (error) {
        console.log('Error: ' + error);
    }
);

Please note that this plugin copy the images it's location temporary location to app directory and returns it's path which is a temporary path so we have to get absolute path to display images. For this modify above code as follow.

window.imagePicker.getPictures(
function(results) {
for (var i = 0; i < results.length; i++) {
console.log(results[i]);

var location= results[i];

window.resolveLocalFileSystemURL(location, function(oFile) {
oFile.file(function(readyFile) {
var reader= new FileReader();
reader.onloadend= function(evt) {
Ext.getStore('ImageGallery').add({
fileURI: evt.target.result
});
Ext.getStore('ImageGallery').sync();
};
reader.readAsDataURL(readyFile);
});
}, function(err){
console.log('### ERR: filesystem.directoryUp() - ' + (JSON.stringify(err)));
});
}

}, function (error) {
console.log('Error: ' + error);
}
);

As you can see in above code, using the temporary location we are getting absolute path of images and adding it to Sencha Touch stores so we use it to display in Sencha Touch data view.

Following is the Store and Model definition.

Ext.define('MyApp.model.ImageGallery',  {
    extend: 'Ext.data.Model',
    config:
    {
        idProperty: 'id',
        fields:
            [

                { name: "id", type: 'int' },
                { name: 'fileURI', type: 'string'}
            ]
    }
});

Ext.define('MyApp.store.ImageGallery', {
    extend: 'Ext.data.Store',
    config:
    {
        autoLoad: true,
        model: 'MyApp.model.ImageGallery',


        proxy:
        {
            type: 'localstorage',
            id: 'image-gallery'
        }
    }
});

And following is the dataview.

 {
xtype: 'dataview',
height: '100%',
width: '100%',
layout: {
type: 'fit'
},
inline: {
wrap: true
},
style: 'background-color:#fefefe',
margin: '6% 6% 6% 6%',
itemTpl: [
 '<div style="width: 140px;height:140px;overflow: hidden">'+
 '<img id="gallery_{id}" style="position: relative;left: 15px;top: -115px;z-index: 1" src="{fileURI}" width="100" height="100"/>'+
 '</div>'

],
store: 'ImageGallery',
itemId: 'imageGalleryDataView',
id: 'imageGalleryDataView',
mode: 'MULTI',
selectedCls: ''
}

Ultimate output will look like this.








Thursday, August 27, 2015

Android Cordova 4.0.2 Webview Intercept Requests

Recently I was working on Android cordova app where I was using cordova 4.0.2 version. In this project I have to intercept urls loaded in webview and do some stuff.

In earlier cordova version we can do following.

myWebView.setWebViewClient(new WebViewClient() {
         public boolean shouldOverrideUrlLoading(WebView view, String url)  {
         }
});

But in newer version of cordova this will not work. So what to do? Here is this blog I am going to explain it.

First of all lets get webView


myWebView = (WebView) this.appView.getView();

Then add WebViewClient to webView.

myWebView.setWebViewClient(new SystemWebViewClient((SystemWebViewEngine) this.appView.getEngine()){
          public boolean shouldOverrideUrlLoading(WebView view, String url) {
          }
          public void onPageFinished(WebView view, String url) {
                   super.onPageFinished(view, url);
          }
});

As you can see in cordova newer version they have added new class SystemWebViewClient in cordova lib that we have to use for accessing functions like shouldOverrideUrlLoading and onPageFinished.

Hope this helps you. 

Wednesday, April 22, 2015

JavaScript identify Android WebView

Hello,

Recently I was working on the cordova project where we have mobile web application hosted on server. With the same URL loaded in Android webview we created native app. Now we have some functionalities in app that should only be available if URL is loaded in Android WebView like camera capture. For that we have to identify webview with JavaScript. In this blog I will explain how to do this.

I added very simple logic. I set custom user agent for WebView from Native android app and just checked that in JavaScript and set global variable. Here is how you can do that.

First of all open res/values/string.xml and add following line.


<string name="user_agent_suffix">AppName/1.0</string>

Then we this Custom Agent from onCreate function of Android Activity.

this.appView.getSettings().setUserAgentString(
            this.appView.getSettings().getUserAgentString() 
            + " "
            + getString(R.string.user_agent_suffix)
         );

So we are setting custom user agent in Android WebView. Now in our app we just have to check this with UserAgent.

Var isNativeApp  = /AppName\/[0-9\.]+$/.test(navigator.userAgent) ? true : false//For checking native app.

That's it now you can check isNativeApp variable anywhere in app to set functions on if the app is running in Android WebView.


Monday, February 2, 2015

Eclipse Android Java Build Path Error


Hello,

This is small and quick blog on an issue I faced today. I was working on Cordova android project and I updated my Java version and JRE and suddenly I got following error after clean and re building entire work space.

The project cannot be built until build path errors are resolved.

I tried several solutions as usual like removing JAR file references and re add them. Quit eclipse and restart it etc. Still the problem persists. So as all software engineer do, I tried to search on Google and Stackoverflow and find out the same solutions which I already tried. I kept looking for some time and quick applying solutions again and again but this error does not go away. So finally I decided to take backup my current project and start making changes in other copy.

Tried so many things and did refresh workspace so many times. Did clean and rebuild entire workspace but it wasn't working. Finally I solved it by following step.

1) Go to project explorer and right click on project
2) Select properties.
3) On left side choose Project References.

Once you select this on left, right side you will get your project references with check marks on left side. If you see them un checked as show in following screenshot.


Then this is the problem. Check all the check boxes of references and click on ok. Now again clean and refresh entire workspace and above error won't be there anymore.

Hope this helps you and saves your time.

Cordova Android Build Failed (build.xml)- Using old SDK path (Cordova Update Android SDK)

Hello,

Recently in one of my cordova project I faced an issue. When I created a project, I was using adt-bundle-mac-x86_64-20131030 SDK. Later I upgraded my SDK to new one adt-bundle-mac-x86_64-20140702. Everything was working good since I updated this new SDK path in my .bash_profile file So creating new project was working.

After few months I have to add a new cordova plugin in project which I crated with old sdk. Plugin was added but when I tried to build android project, the build was failed since it was referring to old SDK path which does not exits anymore. I spent an hour to look for a solution to Google but could't find any solution. Later I checked build.xml file and found out that it was using env.ANDROID_HOME  variable to get SDK path. I added following two lines in .bash_profile.

export ANDROID_HOME=/Users/hirendave/Desktop/adt-bundle-mac-x86_64-20140702/sdk

export PATH=${PATH}:${ANDROID_HOME}/tools:${env.ANDROID_HOME}/platform-tools

And tried to build again but it was not working. Again I spent some time to find out a solution on Google but could not get it working. I again checked build.xml and suddenly following line caught my attention.

<fail
            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
            unless="sdk.dir"
    />

I tried to search local. properties files, it was not there. So I generated it with android update project. Following are exact steps you need to do. First go to your cordova project directory and run following commands.

cd platforms
cd android
android update project -p .

This will update your android project and generated new local.properties files with latest SDK path and other configurations.

Now go to CordovaLib project and repeat the same steps.

cd CordovaLib
cd android update project -p .

This will update your CordovaLib project and generated new local.properties files with latest SDK path and other configurations.

That's it now run following command and it will build successfully.

cordova build android

Hope this helps you and saves your time.

Monday, May 12, 2014

iOS 7 Phonegap Change Background Color of Status Bar

Hello,

Recently I was working on Sencha Touch, Phonegap application where we have a requirement to change background color of top status bar of iPhone where we have carrier, wifi and battery symbols. There are two ways to do it. In this blog I will explain both the steps.

First Step

In iOS 7 if you have status bar visible your UI will overlap the status bar and we will take advantage of it. First select your project and go to Deployment info. Make sure you have Hide during application launch checkbox and set status bar style as default.

Now your webview will overlap the UI and the status bar will be transparent. So we can add a component on top of our page with height 20 pixel and preferred background color so your status bar will have same background color. For example I added docked panel in Sencha Touch with fixed height and background color in my main container. 

Ext.define('MyApp.view.LaunchView', {
    extend : 'Ext.Panel',
    xtype : 'launchmain',

    config : {
        layout : 'card',
        
           items : [{
                    xtype: 'panel',
                    docked: 'top',
                    style: {
                        'background-color': '#34495E',
                        'color':'#ffffff'
                    },
                    height: 20
                    
           }]
    }
});

Since this is may main container all the views added in this container will have this panel and top. Now issue could be if you have some external pages loaded in app on which you don't have control then this panel will not be there you can not have top component with background color. For that follow step 2

Second Step

Here in this step we will add component on top of our webview in our main iOS 7 view. Hence it will be available throughout the app. For that add following code in didFinishLaunchingWithOptions method of AppDelegate.m

 if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
        UIView *topView = [[UIView alloc] init];
CGRect screenRect = [[UIScreen mainScreen] bounds];
        topView.frame = CGRectMake(0, 0, screenRect.size.width, 20);
        topView.backgroundColor = [UIColor colorWithRed:52/255. green:73/255. blue:94/255. alpha:1];
        [self.window.rootViewController.view addSubview: topView];
    }

Here we are added another view with required background color on top of webview. Now we have to shift down webview for 20 pixel so that this view would be visible. For that add following code to viewDidLoad function of your MainViewController.m file. 

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
        CGRect viewBounds = [self.webView bounds];
        viewBounds.origin.y = 20;
        viewBounds.size.height = viewBounds.size.height - 20;
        self.webView.frame = viewBounds;
    }

As you can see in above code we are changing the origin of webview and shift it 20 pixel down. You can simply add above code to avoid overlapping of UI.


Monday, April 21, 2014

Cordova build JAR File From Source Code (For MAC OSX only)

As we know that now Cordova only support command line interface. Earlier with download you get cordova.jar file, that you can directly import to eclipse project and create android cordova file. Now with new download you only get source code, you have to manually generate cordova.jar file and import to eclipse. In this blog I will explain you the steps. First download Cordova source code from the Download.

Extract the content on the desktop. Now you will need following two things.

Apache Ant 1.8.0 or greater
Android SDK which you can download from Android Developer Portal

Download and extract SDK to desktop. Now we will install apache ant. Open the terminal and run following command.

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

After homebrew is installed we will install ant.

brew install ant

Now ant is installed, we will configure modify paths in bash profile file for android SDK. Run following command in terminal

nano ~/.bash_profile

Go to and of the file and paste following code.


export PATH=/Users/yourusername/Desktop/adtdirectotyname/sdk/tools:$PATH

Click Control+X, Y, Enter. It will save your new file. Now go to your Cordova source code and go to cordova-android/framework and run following command.

android update project -p . -t android-19
ant jar

And it will generate cordova .jar file in the framework folder, you can use that file in your eclipse.

Thursday, January 2, 2014

Cordova Android Application in Play Store, Does Not Support Tablet

Hello,

Wish you all a very Happy New Year, This is my first blog of 2014.

Recently we faced a strange issue with our Corodva Application. We built Sencha Touch application and used Cordova as native wrapper. When we uploaded APK to Google Play store, there was a notification that, application does not support tablets.

We were not sure what could be the issue, as we did not specify anything like this in application. First we thought it's a issue related to layouts, as some tablets may have high definition resolution. So we added all the layout files like ldpi, mdpi, hdpi and xhdpi. But still issue was the same. Later on we figured out that, this issue was actually related to permissions mentioned in Android manifest file. Normally when we create phoengap application for android, we specify following permissions.

<supports-screens android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" android:resizeable="true" android:anyDensity="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />

If you see the above permission, we specified permission like RECEIVE_SMS, WRITE_EXTERNAL_STORAGE, RECORD_AUDIO etc. IT may be possible that the tablet only allows Wifi connection and it does not have any sim card or external storage so it may not receive the SMS or record audio or write to external storage. But we requested the permission in manifest file and which was not possible to grant so the application can not work on tablets.

If you face the same issue try and remove the permissions which are not necessary and may not be available in some tablets. After removing permissions, upload APK to Play store and it will work.

Hope this helps you.