Deals

Tuesday, February 24, 2015

Android Obtain Crash Data of Application Remotely

Recently in one my project we distributed APKs to some people to test applications and we got a complain that APP is crashing. While on our side app was not crashing and it was working fine. We were not sure what could be possible reason of crash.  As they were running app from APK we sent in mail so there is no way they can report to Play Store. So we added our own solution to get stack trace on server from the app. In this blog I am going o explain how to do this.

First we added custom exception handler class. Here is that class. 

package com.myapp.app;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;

import android.util.Log;

public class CustomExceptionHandler implements UncaughtExceptionHandler {

    private UncaughtExceptionHandler defaultUEH;
    private String url;
    public CustomExceptionHandler(String url) {
        this.url = url;
        this.defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
    }

    public void uncaughtException(Thread t, Throwable e) {
        Calendar c = Calendar.getInstance();
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String timestamp = df.format(c.getTime());
        final Writer result = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(result);
        String stacktrace = result.toString();
        printWriter.close();
        String time = timestamp + ".stacktrace";
        if (url != null) {
            sendToServer(stacktrace, time);
        }

        defaultUEH.uncaughtException(t, e);
    }

    private void sendToServer(String stacktrace, String time) {
        Log.v("stacktrace","stacktrace");
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost(url);
        List nvps = new ArrayList();
        nvps.add(new BasicNameValuePair("time", time));
        nvps.add(new BasicNameValuePair("stacktrace", stacktrace));
        try {
            httpPost.setEntity(
                    new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
            httpClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

As you can see in above class we have added Class with name CustomExceptionHandler which implements UncaughtExceptionHandler. The constructor is expecting server URL. This is your custom server URL where you want to log stack trace. As you can see in above code we have uncaughtException function which is logging stack trace to server by calling function sendToServer with HTTP POST request. Now We have to add this class as exception handler. This you can do in onCreate of your activity.

Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(
     null, "http://yourloggingurl));

As you can see in above code we are setting default exception handler for thread and passing the URL, you have to replace your own URL instead of yourloggingurl. That's it and your app will send complete stack trace on crash to server. Here is the PHP code on server to get data and write a file if you need.

<?php
    $time = $_POST['time'];
    $stacktrace = $_POST['stacktrace'] ;
    file_put_contents($filename, $stacktrace . "\n", FILE_APPEND);
?>

As yo can see in above codes you are getting stack trace and writing to a file. Make sure you have write permission on directory.


Thursday, February 19, 2015

iOS Cordova Get Device Name

Hello,

Recently in one of my iOS app project, I had a requirement to get device name like "Hiren's iPhone". First I thought it's pretty simple as I checked device API documentation and saw that there is method device.name which should gave device name. I tried that and surprisingly it was returning undefined. I was not sure why it's not returning result. Then I saw source code and found out actually name property is not added in device API so I decided to add it. In this blog I am going to explain how to do this.

First open your CDVDevice.m file and find following function.


- (NSDictionary*)deviceProperties

In this function add following new line. 

[devProps setObject:[device name] forKey:@"name"];

That's it on objective C side. Now lets modify on JavaScript side. Open device.js file inside plugins/org.apache.cordova.device folder in your www folder. There is a constructor function 

function Device()

In this function first add name property. 

this.name = null;

And inside following function initialize this property.

channel.onCordovaReady.subscribe(function() {
        me.getInfo(function(info) {
        }
}

me.name = info.name;

That's it and now device.name should return your name of device set in settings.

Tuesday, February 17, 2015

iOS Today App Extension Widget Tap To Open Containing App

Hello,

Recently I added Today App Extension to one of my iOS app. In that we have a requirement to open containing app when user taps anywhere in app extension view. In this blog I am going to explain how to do this. First you have to add tap gesture recognizer to your main container view. In my case I had UIView as base container view. Inside this view I have added all other views.

So first create iboutlet property for that view. Now create Single Tap Gesture Recognizer.

UITapGestureRecognizer *singleFingerTap =
    [[UITapGestureRecognizer alloc] initWithTarget:self
                                            action:@selector(handleSingleTap:)];

[mainContainerView addGestureRecognizer:singleFingerTap];

As seen in above code, first we created singleFingerTap recognizer and added this as gesture recognizer to mainContainerView. Now add following function which we mentioned in selector.

- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
    NSURL *pjURL = [NSURL URLWithString:@"AppUrlType://home"];
    [self.extensionContext openURL:pjURL completionHandler:nil];
}

That's it. Simple.. isn't it? But wait, it won't work as we have to add URL type in our app. In iOS you can define custom URL schemes and URL types for your app. Using which you can open your app from browser or from some other app using openURL function as shown above in code. So let's add custom URL type for your app.

Open plist file of your main app and add new item with name URL types, expand item 0 of it and add new item with name URL Schemes. Expand item 0 of URL Schemes and add  "
AppUrlType" as a value. For your application, you can use any valid name. After adding this, you should have following structure in your plist file.


That's it. Now select your App Extension Target and run the widget. Tap anywhere in your widget and it will open your containing app. 

iOS App UI not updating in Main Thread

Hello,

Recently in one of my projects, I faced a very strange issue. I have an http service call in app which was in background which brings some data. I want to show those data in Textviews in UI. Now the issue was it was not updating UI properly. I had five textviews and five strings in five variables. Out of which it was updating only one Textviews. Rest of the views were not updated. I was not sure what was the issue here as I was updating UI on main thread but still it was not working. See the below code.


NSString *value1 = [jsonArray objectForKey:@"key1"];
NSString *value2 = [jsonArray objectForKey:@"key2"];
NSString *value3 = [jsonArray objectForKey:@"key3"];
NSString *value4 = [jsonArray objectForKey:@"key4"];
NSString *value5 = [jsonArray objectForKey:@"key5"];

As you see in above code I set five variables from my array which were created from JSON response of web service. Now I used dispatch_async to go on Main thread and set values to Text views.

dispatch_async(dispatch_get_main_queue(), ^{
       [txt1 setText:value1];
       [txt2 setText:value2];
       [txt3 setText:value3];
       [txt4 setText:value4];
       [txt5 setText:value5];
});

As I mentioned an issue above that, it was setting value of only first text views. Others were blank. So I was not sure what was the issue. Later I realized that it was nil problem. Since I used local variables to store data, by the time my code inside dispatch_async runs, the scope of those variables were destroyed and there was a nil value. So other text views were blank.

So the solution was to keep variable initialization inside  dispatch_async method. See the below code.

dispatch_async(dispatch_get_main_queue(), ^{
       NSString *value1 = [jsonArray objectForKey:@"key1"];
       NSString *value2 = [jsonArray objectForKey:@"key2"];
       NSString *value3 = [jsonArray objectForKey:@"key3"];
       NSString *value4 = [jsonArray objectForKey:@"key4"];
       NSString *value5 = [jsonArray objectForKey:@"key5"];

       [txt1 setText:value1];
       [txt2 setText:value2];
       [txt3 setText:value3];
       [txt4 setText:value4];
       [txt5 setText:value5];
});

That's it, it worked. After having initialization inside  dispatch_async method, all the text views values were displayed properly. Hope this will help you and save you time.

iOS Share Data Between iOS App and Today Widget (App Extension)

Hello,

Recently in one of my projects I implemented a Today Widget for the iOS application. While working on that I faced a situation where I have to get data stored in User Defaults of Main app to Today Widget. In this blog I am going to explain how you can sync data between app and widget.  In my case it was simple string data that was stored in user defaults.

For this, first you need to create an app group from Xcode. App group is group of apps which contains main app and an extension. When you create a group from Xcode, it will also create a group in developer portal. For that, first click on project in project explorer and select your main app target in Xcode and go to Capabilities - > App Groups. Initially app groups will be off, first you have to make it on and it will show a pop up window where you can create a new group. Group name always starts with group. prefix. See the image below.


Add your new group like this: group.companyname.groupname and click on Ok. It will sync with developer portal and create app group. Now select your app extension target and go to Capabilities - > App Groups. It will be off first. On it and it will sync with existing app groups which we created in first step. Add your app extension to this group. That's it. Now you can share data between app and app extensions. Now you have to add data by creating group defaults and saving data to groups. See the following code.  This code you can add to your main app.

NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:@"group.company.GroupDefaults"];
[shared setObject:[defaults objectForKey:@"key1"] forKey:@"value1"];
[shared setObject:[defaults objectForKey:@"key2"] forKey:@"value2"];
[shared synchronize];

As you can see in above code, we have created NSUserDefaults class instance with suite name or the group. Every time after adding objects to NSUserDefaults, you have to synchronize it. Else data will not be saved. Add following code to your app extension where you want to read data. 

NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:@"group.company.GroupDefaults"];
self.value1 = [shared objectForKey:@"key1"];
self.value2 = [shared objectForKey:@"key2"];

Also you can save other data in app extension and can read it in main app. 

[shared setObject:[defaults objectForKey:@"key3"forKey:@"value3"];
[shared setObject:[defaults objectForKey:@"key4"forKey:@"value4"];
[shared synchronize];

As I mentioned above, every time you have to sync after adding or modifying data in user defaults. Hope this will help you.



Wednesday, February 4, 2015

Solution for - Magento cart.totals API Returns Zero After Adding Product

Hello,

Recently in one of my project I was working with Magento SOAP APIs. Where we have used Magento cart.create API to create Magento cart and adding items to it. Now every time I add items I want to refresh totals. So I used cart_product.add API and after that used cart.totals API to get totals. But strangely it was always returning zero, no matter how many products are there. I tried to find out a solutions for it and did some tests and in one of the test I found out that if after creating a cart if I do operations like setting shipping address or add coupons, it was giving accurate totals.  That's really strange. I tried to find out issue for it but I am not magento expert to finally I came up with following solution. After creating  cart, I set dummy billing and shipping address immediately. And then when I add a product and get totals, it was giving me accurate result.

Please note this is not the best solution, but if you want to use you can use this. This will solve your problem. If any magento expert can debug API code and resolve this issue please post solution.

So here is what you have to do.

//Create a Quote

$client = new SoapClient($baseurl.'/api/soap/?wsdl');
$session = $client->login(USER,PASS);
$quote_id = $client->call( $session, 'cart.create', array($_storeId) );

//Add dummy shipping and billing address

$arrAddresses = array(
array(
"mode" => "shipping",
"firstname" => "testFirstname",
"lastname" => "testLastname",
"company" => "testCompany",
"street" => "testStreet",
"city" => "testCity",
"region" => "testRegion",
"postcode" => "testPostcode",
"country_id" => "id",
"telephone" => "0123456789",
"fax" => "0123456789",
"is_default_shipping" => 0,
"is_default_billing" => 0
),
array(
"mode" => "billing",
"firstname" => "testFirstname",
"lastname" => "testLastname",
"company" => "testCompany",
"street" => "testStreet",
"city" => "testCity",
"region" => "testRegion",
"postcode" => "testPostcode",
"country_id" => "id",
"telephone" => "0123456789",
"fax" => "0123456789",
"is_default_shipping" => 0,
"is_default_billing" => 0
)
);
$resultCustomerAddresses = $client->call($session, "cart_customer.addresses", array($quote_id, $arrAddresses));


//Now add a product to cart

$resultCartProductAdd = $client->call(
$session,
"cart_product.add",
array(
$quote_id,
$arrProducts
)
);

//Now get the cart info

$result = $client->call($session, 'cart.totals', $quote_id);
echo json_encode($result);

Hope this helps you and solve your problem.

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.