Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Monday, September 7, 2020

Connect to Web Socket From Your Android App using OkHttp

Hello,

In this blog I am going to explain how you can connect with Web Socket from your Android App using OkHttp. This will be useful when you want to send some data real time from your Android app to Web app. 

First add following line in your build.gradle file dependencies. 

implementation 'com.squareup.okhttp3:okhttp:3.11.0'

Now let the gradle build and sync. Next we will make a class which extends WebSocketListener from OkHttp3 and a new class and name it MyWebSocketListener.java and add following code to it. 

import okhttp3.Response;

import okhttp3.WebSocket;

import okhttp3.WebSocketListener;

import okio.ByteString;

public final class MyWebSocketListener extends WebSocketListener {

    private static final int NORMAL_CLOSURE_STATUS = 5000;

    public MyWebSocketListener() {  

    }

    @Override

    public void onOpen(WebSocket webSocket, Response response) {

        webSocket.close(NORMAL_CLOSURE_STATUS, "Exit");

    }

    @Override

    public void onMessage(WebSocket webSocket, String text) {

        //String Meesage received

    }

    @Override

    public void onMessage(WebSocket webSocket, ByteString bytes) {

        //Meesage received in form of binary data

    }

    @Override

    public void onClosing(WebSocket webSocket, int code, String reason) {

        webSocket.close(NORMAL_CLOSURE_STATUS, null);

        //Closing socket

    }

    @Override

    public void onFailure(WebSocket webSocket, Throwable t, Response response) {

        t.printStackTrace();

    }

}

Now we have listener created. Let's create client and attach it to Web Socket and Listener.

val listener = MyWebSocketListener()

val webSocket =   OkHttpClient.Builder().build().newWebSocket(request, listener)


That's it, now you can use this instance to send or receive message.

Tuesday, August 4, 2020

Android Build Error - The Crashlytics build ID is missing

Hello,

Recently I faced a very strange issue in one of my android app. We were using Fabric IO for crash analytics earlier in the app and since now it's already merged with Firebase Analytics. we need to update the app. 

I removed Firebase maven repository and removed all plugins from build gradle file and removed Fabric code from everywhere. And build was successful. After that I followed all the instructions on getting started page of Firebase and crash analytics and build was successful.  And then problem started. Once we run the app, it crashes and shows following error in Logcat. 

"The Crashlytics build ID is missing. This occurs when Crashlytics tooling is absent from your app's build configuration. Please review Crashlytics onboarding instructions and ensure you have a valid Crashlytics account

Now it seems there is an issue with Firebase crash analytics account so I regenerated the google service json file add updated in project. But it didn't help. 

Searched on Google, answers suggest to put back Fabric plugin again in gradle file. Which does not make any sense to me. Since Fabric is out dated, why do we need to add it again. 

Finally after 2 days I was able to solve the problem. Here is the solution

Remove google service json file. Use Firebase assistant and connect with your account again. Select the project and it again generate google service json file. Keep link to fabric io repository in top level gradle file.

maven { url 'https://maven.fabric.io/public' }

I don't know how it worked but it works and I really don't know the reason of why it didn't work and why it started working after above solution. But it does solve the problem. 

If anyone who is reading this blog, knows the reason please put in comment. 

Sunday, March 22, 2020

Socket.io Not Working on Android 9 (API 28)

Hi,

Recently in one of the project we faced a situation where we have used socket.io on backend and Android app connects to it. Socket.io was using http protocol for testing. It worked fine in older android versions but faced an issue in Android 9 and above where socket was connected but emit was not getting on server.

After couple of hours struggle finally found the problem. The problem was socket io was using http protocol and it's clearText HTTP request and it is banned in recent android version considering security reasons.

In earlier android version by default clearText is set as true but in later version default value is false. That means android components like HTTP and FTP and classes DownloadManager, and MediaPlayer can not use HTTP.

To over come this issue and allow socket to connect with HTTP protocol you have to allow clearText traffic on app level and for that you have to add following attribute in Application tag of your AndroidManifest.xml

<application
        android:usesCleartextTraffic="true">
.....
</application>

Once you add this to your manifest file, your socket emit will start working and you can still use HTTP with your socket.

Please note that since it was testing app we used HTTP based socket. However in production you should always use HTTPs protocol.

Hope this helps you.


Saturday, December 16, 2017

Android RecyclerView Add Load More Functionality

Hello,

Recently I was working on adding load more functionality on Android RecyclerView. The purpose was to load more data as soon as user scrolls to bottom and there are no records left.

Since we already have used swipe to refresh plugin other load more plugin was not working as the event was not attached and fired. If we remove swipe to remove then it worked but we needed both functionalities. So for that we did simple trick. Here is the code.

testRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        if (!recyclerView.canScrollVertically(1)) {
            Toast.makeText(getApplicationContext(),"CALL LOAD MORE FUNCTION TO LOAD MORE DATA",Toast.LENGTH_LONG).show();
        }
    }
});

So trick is very simple, we added on scroll listener to recycler view and just checked it can not scroll any more vertically that means we reached at bottom and form here we can add logic to load more data.

Hope this helps you.

Saturday, February 11, 2017

Effective Way to Create and Save File In Android Application

Hello, in this blog post I am going to explain effective way of creating and saving file in android application. Also will explain what are the general mistakes developer make in creating and saving file and how to prevent it.

Why it is important to have strategy for File create and Save in Android?



Because android file system is very similar to disk based file system and when there is a problem in creating or saving file it throws IOException so your application may crash while it's running and it's not good for professional or business application. So you need a proper strategy to create and save file and you have to add all kind of checks and exception handling for this.


Common mistake by developers on Creating and Save file in Android


Any new developer who is working first time on the file make this mistake. They don't know how to create a file and they do not refer to android developer site and android SDK document. They just search on web and find answers on site like StackOverFlow and copy paste the code. But sometime what happens is the code which they have used was about saving file in SD card and it may be possible that some phones do not have SD card so when the application is running in these phones, it crashes.

Another mistake is, in the code they hard code the path of saving file. Each android version and phone has different way of handling SD card so if you hard code the path it may work in your phone but it does not work in other phone. So here you need some strategy for this and make sure that your code works in all scenarios.  Here are some common things you should consider while creating and saving file.


  1. Decide where you want to store file, in external storage or internal storage.
  2. If storing in internal storage make sure there is space available.
  3. If storing in external storage, first check if there is external storage is available and there is a space.
  4. Make sure you have permission to store files in external storage. 

If you have requirement that file created should only be accessed by your app, you should create it in internal storage in app directory or else you should create it in external storage. Here is the code I have used in one of my app to create file in external storage.

For this you should also have permission mentioned in your android manifest file.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;

private static File getOutputMediaFile(int type){
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.

File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
 Environment.DIRECTORY_PICTURES), "YOURAPP");

// Create the storage directory if it does not exist
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("YOURAPP", "failed to create directory");
return null;
}
}

// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
if (type == MEDIA_TYPE_IMAGE){
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
} else if(type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"VID_"+ timeStamp + ".3gp");
} else {
return null;
}
return mediaFile;
}

As you see in above code, we are first checking if external storage is available or not. If it's not available, you just return null and if it's available, depending on file you want to create, create unique file name with current time stamp. Do not use hard coded file name like myVideo.3gp and it will always overwrite the previously created file.

Now we will use above function to check if we have file created or not in external storage and if not created we will create it in internal storage.

try{
File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);

if(outputFile == null){
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
outputFile = new File(getFilesDir() + "/"  + File.separator +
"VID_"+ timeStamp + ".3gp");
}
} catch (IOException e) {
Log.d(TAG, "IOException creating file : " + e.getMessage());
}

As you see in above code we are calling this function to create file of type video and check if it returns null. If it returns null then we are creating file in internal storage. Following line creates file in internal directory.

outputFile = new File(getFilesDir() + "/"  + File.separator +
"VID_"+ timeStamp + ".3gp");

And we have enclosed code try catch block to catch any possible IOException. In case of exception, you should properly display a message to user.

Friday, February 3, 2017

Android SDK Content Loader failing with NullPointerException

I have just recently started getting an error any time I interact with the Android SDK and AVD Manager in Eclipse. It does not load SDK manager and when I try to open AVD manager it says please set "Android SDK path in preference". This was strange error I started getting since I added few AVDs with Google API and when I check log of NullPointerException it was showing error in AVD Info class so I was sure that error is related to AVD. May be I misconfigured AVD while creating and now it's not allowing me to do any work in Eclipse.

So to solve this error you have to delete .android directory located in your user profile folder if you are using Windows and if you are using Mac, this will be hidden directory in your home directory.

So first of all make all the hidden directory visible. First of all open terminal and type following commands.

defaults write com.apple.Fider AppleShowallFiles YES

Now you have to kill all the finders and then go to home directory.

Killall Finder

Now when you go to home directory you will see .android directory which was hidden.


Delete that directory and even delete it from trash.

Now restart the Eclipse and try to load AVD manager and Android SDK manager and it should work fine.

NOTE : Please note if you delete .android directory, you will loose all the AVD you have created before and some of your preference of eclipse, so please use it carefully. 

Monday, January 2, 2017

Android MediaRecorder Stop Failed - 1007

If you have ever used Android MediaRecorder to record video or audio in your android application, you must have faced this error. Recently I had nightmare in using MediaRecorder in my android application. I think it's most unstable class in Android SDK and there is a different behavior in each device. In this blog I am going to explain more about this and possible resolution of this.


First lets see wha's the meaning of this error : Android MediaRecorder Stop Failed - 1007

There is no documentation on Android developer site about this error code but from my experience I found out that there are three reasons behind this error

1) You called stop method too early.

According to documentation on MediaRecorder.java class it says

Stops recording. Call this after start(). Once recording is stopped, you will have to configure it again as if it has just been constructed. Note that a RuntimeException is intentionally thrown to the application, if no valid audio/video data has been received when stop() is called. This happens if stop() is called immediately after start(). The failure lets the application take action accordingly to clean up the output file (delete the output file, for instance), since the output file is not properly constructed when this happens.

That means if you start recording and in couple of seconds you call stop recording there is no significant data of recording hence stop method throws an exception. So you must wait for sometime. From my testing I found out that you should at least for minimum 10 seconds. So the solution is to have stop method called inside try catch block and handle the exception properly or do not allow user to stop recording till 10 seconds of start of recording. You can either disable stop button and make it enable after sometime. Once the exception is thrown, you should clean the the resources, release media recorder and camera and all other objects.

2) Error in Writing to Output file

We have to set output file to MediaRecorder when we start recording. When you stop all the recorded data will be appended in output file specified by you on start. This could failed due to reasons like insufficient space to write file or you don't have permissions to write to external and internal storage due in Android 6.0 on words.

3) MisMatch in preview size and video size set in MediaRecorder

I have mentioned about this in my previous blog. You can read it here.

Android MediaRecorder Start Failed - 19

Hope this helps you and save you time.

Friday, December 30, 2016

Android MediaRecorder Start Failed - 19

If you have ever used Android MediaRecorder to record video or audio in your android application, you must have faced this error. Recently I had nightmare in using MediaRecorder in my android application. I think it's most unstable class in Android SDK and there is a different behavior in each device. In this blog I am going to explain more about this and possible resolution of this.

First lets see wha's the meaning of this error : Android MediaRecorder Start Failed - 19

There is no documentation on Android developer site about this error code but from my experience I found out that it's because of mis match in size of video preview frame and MediaRecorder video size and this error comes randomly in different devices. The best approach is to use CamCorder profile as mentioned on Android developer site.

CamcorderProfile profile =CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
mMediaRecorder.setProfile(profile);

When you use this most of the following default values are set in MediaRecorder from the selected profile.


  • OutputFormat
  • AudioEncoder
  • VideoEncoder
  • VideoSize
  • VideoFrameRate

But this does not work on all the devices as some the devices don't accept these default values hence MediaRecorder does not work and specifically due to VideoSize it gives error. So to solve this you should manually set all the values. Following settings works on almost all devices

int width = mCamera.getParameters().getPreviewSize().width;
int height = mCamera.getParameters().getPreviewSize().height;

mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
mMediaRecorder.setVideoSize(width, height);
mMediaRecorder.setVideoFrameRate(30);

However there are still some problem with it. When I tested in some high resolution devices. I got following width and height from getPreviewSize()

2048
1536

But MediaRecorder failed to start and when I checked error log I got error.

Unsupported Video Dimension 1920 X 1688

That means it changed the dimension by itself and ignored the width and height I set it in setVideoSize. So that means you can not depend on it. So what's the solution for this. Well the solution is find the optimal video size based on your preferred frame dimensions and available supported size in your device. Following is the function I used for it.

private Size getOptimalPreviewSize(List sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.2;
double targetRatio = (double) w / h;
if (sizes == null) {}
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}


List sizes = mCamera.getParameters().getSupportedPreviewSizes();
Camera.Size optimal = getOptimalPreviewSize(sizes, 640, 480);

if(optimal != null){
           
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
mMediaRecorder.setVideoSize(optimal.width, optimal.height);
mMediaRecorder.setVideoFrameRate(30);      

}else{

int width = mCamera.getParameters().getPreviewSize().width;
int height = mCamera.getParameters().getPreviewSize().height;

mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
mMediaRecorder.setVideoSize(width, height);
mMediaRecorder.setVideoFrameRate(30);      

}    

For me above solution worked on almost all devices, first we get optimal size from available supported sizes and if there is no supported sizes then we use default preview size.

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 9, 2016

Digital Brochures For Builders, Real Estate Developers

World is going towards digitization but Real Estate Sector is still not using digital technology at it's best. They are still following the old approach of printed brochures to show their development informations and all. This is not just costing them much but this is no more a convenient way.

When I have a smart phone in my hand why should I carry printed, bulky brochure, that can be mis placed or won't be able to give me all the information I need. Instead of that if we have digital brochure, you can just have it in your smart phone. You can check it anytime, it does not consume much space and will be available anytime.


For the digital brochures, a mobile application is the best way to deliver. If you are in real estate business, then having mobile app for your projects will be one of the best way to market your projects and get more customers. A mobile application with all the features to showcase your project can boost your sales and customer engagements.

Mobile application can easily be downloaded and installed in phone. Your customer can find it and can see your projects. You can send notifications to customers for your upcoming projects. You can show entire gallery of your projects. You can show your project location on map, so customer can see exact location and app can guide them to reach at your project easily instead of manually finding a way.

I will give you more information by giving an example of the app we created for leading real estate developer of Gujarat.

Recently we created Digital Brochure Mobile Application for KP Sanghvi Infra for show casing their projects. I will give more informations about the features we added in the app. You can check application on Google Play Store


1) Show your projects according to category like Residential and Commercial



2) Show list of the projects according to selected category.



3) Let the user filter it based on project status and city



4) Show All the Information about selected project.


5) Show Project Gallery with filter like Floor Plan, 3D Photos etc, Since it's digital photographs, there is no limit.



6) Show Exact Project Location on Map and Guide user to come to your location easily.




So with this approach, your customer will have all your information in their. They can check and share it with others anytime. You can send notifications to customers. 

So go digital with this solution, if you need one contact us at.

Email : info@thedesignshop.co.in

Thursday, December 8, 2016

5 Simple Questions To Decide Hybrid vs Native Mobile App Development



If you’re confused and wondering whether to build a hybrid mobile app or a native mobile app, this article will help you decide the mobile app strategy.

Quick introduction to Hybrid and Native app

Hybrid App: Developer wraps web code (HTML / CSS / JavaScript) with native SDK. Can be easily deployed across multiple platform and is usually the cheaper and faster solution.

Native App: This is platform (iOS, Android etc.) specific and requires unique expertise. However the full potential of the platform can be leveraged which will drive great user experience and larger app capabilities (especially around phone hardware).

Following 5 simple questions will help you decide between Hybrid vs Native App Development.

1) Do you want to use Hardware and Native Features.

In your application if you want to use phone hardware like GPS, Camera, SD card etc, it's recommended to go for native app instead of hybrid app. Because native SDK has support to access hardware, For hybrid app depending on the framework, you may or may not hardware access. Also you have to consider performance as well. For example in one my project there was a requirement to get camera preview in the app and capture it. Initially I build hybrid app but camera preview was sluggish and slow so later I have to move to native app. If there is no requirement to access hardware then hybrid application is the best option.

2) Is the UI experience is more important in your application?

If you want to create an insane user experience, the native app approach would do better. A hybrid app can never match the level of user experience that you get in a native app. However, this doesn’t mean that the user experience of a hybrid app is bad. A good front-end developer in hybrid app can get close to a native experience. Also performance of native app is much better than hybrid app so when there is a high demand of performance, go for native app.

3) Does your app need background services?

If your application need to work in background like background location tracking, file download in background then native app is the best option as native SDK has classes to create background services that can be invoked by alarms etc. In hybrid app, if app is background or killed, all the process stops.

4) What is your Development Time and Budget?

If you have very limited budget and want to get app quickly to the market then hybrid app is the best option as you don't have to create separate application for each platform. One single code wrapped with multiple native wrappers will give you native application for different platform so it will save both time and cost. As for the single native app you have to hire native developers, while for hybrid app one developer is enough and it can be quickly developed and deployed to multiple platform.

5) Does your application need offline storage?

Most of the apps are built work offline and for this we need local database storage in app. If your application need more space for offline storage than native app is best option. However it's possible to have offline storage in hybrid app too. But there is a limitation up to certain MB. After that it does not allow more offline storage.


With these 5 questions you can define your development strategy.

Tuesday, December 6, 2016

Android SDK Missing Google Play Services In Revision 30

Recently I faced an issue with my Android SDK, I updated Google Play Services to Revision 30 using SDK manager from eclipse and after update sdk/extras/google/google_play_services/libproject folder was missing and all of my projects gave me error where I have added google play services lib was added.

Now this is bit strange. I checked my SDK folder and find out that instead of 

sdk/extras/google/google_play_services/libproject 

We now have

/sdk/extras/google/m2repository/com/google/android/gms

Where we have play services folder, inside which there are various version folders and it has .aar and .pom file. 

I was not sure how to use that with Eclipse. One option was to create jar file form aar file and add it external lib. But somehow it did not worked. 

I am not sure why Google has break down Google Play Services like this. It may be because Android Studio is now official IDE for android development and eclipse is no longer supported. However my problem was still not resolved as I have native eclipse project and in migrating it to Android Studio, I was facing lots of issues. 

Finally I found out solution and that is to switch to revision 28. You can download Google Play Services Revision 28 from Google Repository. Here is the link.

https://dl-ssl.google.com/android/repository/google_play_services_8298000_r28.zip


Download and extract it to separate folder and import it to your eclipse project. That shall solve your issue. 

Hope this post helps you. 

Saturday, October 1, 2016

Android Adding Button Click Action In Notification Bar

Hello,

Recently in one of my project we have a requirement to add button in notification area and do some actions when button is clicked. So here in this blog I am going to explain how to do this.

First of all Add following XML file in your layout and name it as custom_notification. This will have definition for button we are going to add in notification bar.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="fill_parent"
    android:gravity="center_vertical"
    android:orientation="vertical" >

<Button 
        android:id="@+id/myButton" 
        android:layout_width="wrap_content" 
        android:layout_height="40sp" 
        android:layout_weight="1" 
        android:text="Click Me"
        android:textColor="#000000"
        android:textSize="10sp">
    </Button>


</LinearLayout>

Now in your MainActivity add following function and call it in onCreate method to create notification with button.

public void createNotificationInNotificationBar(){
int icon = R.drawable.ic_launcher;
long when = System.currentTimeMillis();
Notification notification = new Notification(icon, "Click Me To Do Some Action", when);

NotificationManager mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
contentView = new RemoteViews(getPackageName(), R.layout.custom_notification);
notification.contentView = contentView;
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.contentIntent = contentIntent;
notification.flags |= Notification.FLAG_NO_CLEAR; //Do not clear the notification
}

Above code will create notification with button. User will not be able to clear this notification. Now we will add click action handler to button. Add following lines at end of above function.

Intent myIntent = new Intent("button_clicked");
PendingIntent pendingSwitchIntent = PendingIntent.getBroadcast(this, 0, myIntent, 0);
contentView.setOnClickPendingIntent(R.id. myButton, 
                pendingSwitchIntent);

Now add following code to your Manifest.xml file.


This will create receiver and action. Now add receiver to your main activity.

public static class RecordShareReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent myIntent = new Intent(context.getApplicationContext(), ButtonClickActivity.class);
myIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(myIntent);
}
}


This will start ButtonClickActivity. 

public class ButtonClickActivity extends Activity{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//Do your actions here
finish();
}
}

As you can see in above in onCreate method you can do the actions as per your requirement. Once the action is done we finish activity.

Add following code to your android manifest for activity.


android:label="@string/app_name" 
android:launchMode="singleTask" 
android:name=". ButtonClickActivity" 
android:screenOrientation="portrait" 
android:theme="@android:style/Theme.Translucent.NoTitleBar" />


As you can see above we set theme as Translucent theme so activity will not be visible. It will just start do the action and gets finished. 




Saturday, August 13, 2016

Android Record Video in Background Without Preview

Hello,

Recently in one of my project we have a requirement to record video in background with just one button click. No need to display preview or no need of user action, like tap record button. And also stop video recording with just one button click.

In this blog I am going to explain how to do this.

So I have used Android Media Recorder for the same. Since it was not working without preview I added SurfaceView with hidden mode by setting height to 1 so nobody can see it.

First of all lets add some global variables.

View webview;
private Camera mCamera;
private SurfaceView mPreview;
private MediaRecorder mMediaRecorder;
File outputFile;
public static final int MEDIA_TYPE_IMAGE = 1;

public static final int MEDIA_TYPE_VIDEO = 2;

Since we are adding surface view dynamically, add following class to your activity.

    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        private SurfaceHolder mHolder;
        private Camera mCamera;

        public CameraPreview(Context context, Camera camera) {
            super(context);
            mCamera = camera;

            // Install a SurfaceHolder.Callback so we get notified when the
            // underlying surface is created and destroyed.
            mHolder = getHolder();
            mHolder.addCallback(this);
            // deprecated setting, but required on Android versions prior to 3.0
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        public void surfaceCreated(SurfaceHolder holder) {
            // The Surface has been created, now tell the camera where to draw the preview.
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.d(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }

        public void surfaceDestroyed(SurfaceHolder holder) {
            // empty. Take care of releasing the Camera preview in your activity.
        }

        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
            // If your preview can change or rotate, take care of those events here.
            // Make sure to stop the preview before resizing or reformatting it.

            if (mHolder.getSurface() == null){
              // preview surface does not exist
              return;
            }

            // stop preview before making changes
            try {
                mCamera.stopPreview();
            } catch (Exception e){
              // ignore: tried to stop a non-existent preview
            }

            // set preview size and make any resize, rotate or
            // reformatting changes here

            // start preview with new settings
            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();

            } catch (Exception e){
                Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    }  

Now first of all lets prepare everything for for recording.

Button startRecording = (Button) findViewById(R.id.startRecording);
startRecording.setOnClickListener( new OnClickListener() {
@Override
public void onClick(View v) {
               initVideoReocrding();
}
});


 public void initVideoReocrding(){
    // Create an instance of Camera
        appView = (Button) findViewById(R.id.mainView)
        mCamera = getCameraInstance();

        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(this, mCamera);
        mPreview.setId(107);
        
        android.widget.FrameLayout.LayoutParams params = new android.widget.FrameLayout.LayoutParams(1, 1);
        mPreview.setLayoutParams(params);
        ((ViewGroup) appView.getView().getParent().getParent()).addView(mPreview); 
    }

Since we are adding surface view dynamically we have to call start recording function after sometime.

final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
  @Override
  public void run() {
    startVideoRecording();
  }
}, 1000);

public void startVideoRecording(){
if(prepareVideoRecorder()){
Log.v("MediaRecorder","MediaRecorder is ready");
mMediaRecorder.start();
}
}

Here are the additional function we need for this.

public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}
catch (Exception e){
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}

private void releaseMediaRecorder(){
if (mMediaRecorder != null) {
mMediaRecorder.reset();   // clear recorder configuration
mMediaRecorder.release(); // release the recorder object
mMediaRecorder = null;
mCamera.lock();           // lock camera for later use
}
}

private void releaseCamera(){
if (mCamera != null){
mCamera.release();        // release the camera for other applications
mCamera = null;
}
}

/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){
 return Uri.fromFile(getOutputMediaFile(type));
}

/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.

File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
 Environment.DIRECTORY_PICTURES), "MYAPP");
// This location works best if you want the created images to be shared
// between applications and persist after your app has been uninstalled.

// Create the storage directory if it does not exist
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("MYAPP", "failed to create directory");
return null;
}
}

// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
if (type == MEDIA_TYPE_IMAGE){
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
} else if(type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"VID_"+ timeStamp + ".mp4");
} else {
return null;
}

return mediaFile;
}

private boolean prepareVideoRecorder(){

Camera temp = getCameraInstance();
if(temp != null){
mCamera = temp;
}
mMediaRecorder = new MediaRecorder();

// Step 1: Unlock and set camera to MediaRecorder
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);

// Step 2: Set sources
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

// Step 4: Set output file
outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
mMediaRecorder.setOutputFile(outputFile.toString());

// Step 5: Set the preview output
mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());
// Step 6: Prepare configured MediaRecorder
try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
} catch (IOException e) {
Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
}
return true;
}


And following is the function to stop recording.

public void stopVideoRecording(){
    mMediaRecorder.stop();  // stop the recording
        releaseMediaRecorder(); // release the MediaRecorder object
        mCamera.lock();  
        mCamera.stopPreview();
        Log.v("OutputFile", outputFile.getAbsolutePath());
        
        MediaScannerConnection.scanFile(getApplicationContext(), new String[]{outputFile.getAbsolutePath()}, null, null);
        View previewView = ((ViewGroup) appView.getView().getParent().getParent()).findViewById(107);
        ((ViewGroup) appView.getView().getParent().getParent()).removeView(previewView);
    }
    




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.