2CheckoutWrapper – lightweight library for acquiring 2Checkout (payment gateway) token

If you’re creating application that involves purchases, chances are that you’ll need to implement some payment gateway. Most popular choices are Stripe, PayPal, which have dedicated SDKs for Android. There other players in the field and one of them is 2Checkout. You might go with this payment gateway due to the coverage that it provides and because of the fact that can tunnel PayPal payments too.

Unlike the first two mentioned, 2Checkout doesn’t have dedicated SDK or library for Android, though the official Java library will do the trick in general case. The library might add some overhead on your existing code and if you have complex project at hand – you’ll need something light to go with. There is also one more, very important part – how the payment is implemented, the smartest thing of course is to do the payment on  the backend and just use payment token retrieved from 2Checkout when the user provides the credit card credentials. That’s the main motivation why I created the 2CheckoutWrapper (library).

main_logo

Let’s see under the hood.

First and very important part is to utilize 2Checkout’s javascript library and follow the instructions of how to get thetoken via the javascript library. We implement that in one html file (stored in assets folder of the library module) that we load in WebView with layout_width and layout_height of 0dp. Javascript is enabled and the callback is caught via @JavascriptInterface.

The 2checkout.html:

<html>
<body>
<script type="text/javascript" src="https://www.2checkout.com/checkout/api/2co.min.js&quot;&gt;&lt;/script&gt;
<script type="text/javascript">
            var jsonData;

            function postMessage(message) {
                console.log('postMessage')
                console.log(message)
                TwoCheckoutWrapper.on2CheckoutTokenError(message);
            }

            var successCallback = function(data) {
                 //data.response.token.token
                 console.log('successCallback')
                TwoCheckoutWrapper.on2CheckoutTokenSuccess(data.response.token.token);
            }

            // Called when token creation fails.
            var errorCallback = function(data) {
                if (data.errorCode === 200) {
                    generateToken(jsonData);
                } else {
                    postMessage(data.errorMsg);
                }
            };

            function generateToken(env, cardData) {
                console.log('function generateToken')
                jsonData = JSON.parse(cardData)

                console.log('after jsonData')
                var isOnLine = window.navigator.onLine;
                console.log(isOnLine)
                if(isOnLine){
                console.log('is onLine')
                TCO.loadPubKey(env, function() {
                               TCO.requestToken(successCallback, errorCallback, jsonData);
                               });
                }

                else{
                console.log('not onLine')
                TwoCheckoutWrapper.on2CheckoutTokenError(message);
                }

            }
</script>
</body>
</html>

The TwoCheckoutWrapper class:

 


public class TwoCheckoutWrapper{

    private WebView mWebView;
    private TwoCheckoutTokenCallback mTwoCheckoutTokenCallback;

    @SuppressWarnings(&quot;all&quot;)
    public void initTwoCheckoutWrapper(Context c) {
        View view = LayoutInflater.from(c).inflate(R.layout.two_checkout_layout, null);
        mWebView = (WebView) view.findViewById(R.id.two_checkout_web_view);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.addJavascriptInterface(this, Constants.TWO_CHECKOUT_WRAPPER);
        mWebView.loadUrl(Constants.TWO_CHECKOUT_HTML);
    }

    public void get2CheckoutToken(TwoCheckoutCard card, String environment) {
        if (mWebView != null &amp;amp;&amp;amp; card != null) {
            mWebView.loadUrl(&quot;javascript:generateToken('&quot; + environment + &quot;','&quot; + card.getCardValuesAsJSParam() + &quot;')&quot;);
        }
    }

    public void setTwoCheckoutTokenCallback(TwoCheckoutTokenCallback twoCheckoutTokenCallback) {
        this.mTwoCheckoutTokenCallback = twoCheckoutTokenCallback;

    }

    @JavascriptInterface
    public void on2CheckoutTokenError(String error){
        mTwoCheckoutTokenCallback.on2CheckoutTokenError(error);
    }

    @JavascriptInterface
    public void on2CheckoutTokenSuccess(String token) {
        mTwoCheckoutTokenCallback.on2CheckoutTokenSuccess(token);
    }

}

 

TwoCheckoutCard class represents the card data, plus the credentials that the developer enters when seller is created in 2Checkout.

And that’s it. See the implementation here.

Posted in Android, AndroidDev, snippet | Tagged , , , | Leave a comment

Optimizing frame animations: load .gifs

There are tons of great animation approaches and libraries that deal with UI challenges, yet you have this specific case where you have to resort to loading custom animation that has been given to you in form of frames (.png resources; see how that’s done in this tutorial or the official guidelines). That’s fine, right? You’ll just create xml animation and you’re done. Well, if you have a lot of images (100+ frames) the process can be a bummer – I have an idea what you can do – create a gif and load it in an ImageView.
Let’s say you have something like this:

frame_to_gif

What you need to next is to create gif. You can use Photoshop or some other software to do it, but I propose using gifmaker – by far the best web tool for creating gifs from image frames. After you upload the images in the right sidebar you can adjust the speed and size of the gif, set value for looping etc. Here is how the animation looks like converted into gif (yes, I know, it looks dorky).

animation

After this, add animation.gif (or whatever you name your newly created .gif) into res/drawable folder. For image loading use Glide (I prefer it over Picasso, if you’re up for reading session on the subject, click here), hence you need to add this in your build.gradle:

//last version at the time of this blog post
compile 'com.github.bumptech.glide:glide:3.6.1'

In your Activity/Fragment/DialogFragment just add this:


//use ButterKnife
@Bind(R.id.imgGif)
ImageView mImgGif;

//...

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.your_fragment_or_activity_layout, container, false);
    ButterKnife.bind(this, v);

    //load the animation
    GlideDrawableImageViewTarget imageViewTarget = new GlideDrawableImageViewTarget(mImgGif);
    Glide.with(this).load(R.drawable.animation).into(imageViewTarget);

    return v;
}

 

You’re done.

Note: downside – this method will prevent you to readjust the time period of particular frame as opposed to loading xml animation resource.

 

Posted in Android, AndroidDev, snippet | Tagged | 3 Comments

How to do “userless” Reddit OAuth 2 on Android with Retrofit (Tutorial)

Reddit has been one of Internet’s most interesting places. In fact you may freely label it as the front page of the Internet (cheesy pun intended). It gathers (users share) a lot of useful, fun, educational material (and some porn stuff) in form of links and it orders them by user rating. It’s a great piece of content if you want to develop something on top of it. You can visit the official API GitHub here, and you can read the policy rules here. There is dedicated subreddit for developers that use reddit content, and you can see it here.

I, like many, had some (Android) application ides that involved using reddit content, but not involving user interaction in any form, yet the new policy rules state that every content crawling action has to go through OAuth 2 authentication and further approval form the licensing team (which I won’t cover in this blog post, but you see this for further info). This kind of authentication is so called userless and it has some specifics. That is covered here in the official reddit GitHub, yet there’s no example on how to achieve that (has one for iOS, though).

With some trial and error (and some help) I’ve managed to get the authentication working and in order to do that I’ve used Retrofit and jsonschema2pojo for the response objects. So, let’s see how it’s done.

First you need to declare the client class and the needed interfaces. There are two interfaces declared, one for authentication and another for fetching content from any given subreddit (the subreddits’ path is declared as input when module is called).


public class RedditAPI {


    private static RedditAuthInterface sRedditAuthInterface;
    private static GetSubReditInterface sGetSubReditInterface;



    /*Auth*/
    public interface RedditAuthInterface {
        @POST(Urlz.REDDIT_OATH2_PATH)
        void redditAuth(@Body TypedInput body, Callback<TokenResponse> result);
    }

    public static RedditAuthInterface sRedditAuth() {
        if (sRedditAuthInterface == null) {

            RestAdapter restAdapter = new RestAdapter.Builder()
                    .setClient(getAuthClient())
                    .setEndpoint(Urlz.BASE_REDDIT_URL).build();
            sRedditAuthInterface = restAdapter.create(RedditAuthInterface.class);
        }

        return sRedditAuthInterface;
    }


    /*Get Subreddit*/
    public interface GetSubReditInterface {
        @GET(Urlz.SUBREDDIT_PATH)
        void getSubredditContent(@Path(Urlz.SUBREDDIT) String subreddit, @Query(Urlz.LIIMT) String limit, @Query(Urlz.AFTER) String after, Callback<SubredditContentResult> result);

    }

    //the adapter
    public static GetSubReditInterface sSubRedditContent() {
        if (sGetSubReditInterface == null) {

            RestAdapter restAdapter = new RestAdapter.Builder()
                    .setClient(getConfuguredClient())
                    .setRequestInterceptor(getRequestInerceptorToken())
                    .setEndpoint(Urlz.BASE_O_REDDIT_URL).build();
            sGetSubReditInterface = restAdapter.create(GetSubReditInterface.class);
        }

        return sGetSubReditInterface;
    }


    /*Helper methods*/
    private static OkClient getConfuguredClient() {

        final OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.setReadTimeout(Static.READ_TIMEOUT, TimeUnit.SECONDS);
        okHttpClient.setConnectTimeout(Static.CONNECT_TIMEOUT, TimeUnit.SECONDS);
        return new OkClient(okHttpClient);
    }


    private static OkClient getAuthClient() {

        final OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.setReadTimeout(Static.READ_TIMEOUT, TimeUnit.SECONDS);
        okHttpClient.setConnectTimeout(Static.CONNECT_TIMEOUT, TimeUnit.SECONDS);
        okHttpClient.setAuthenticator(new Authenticator() {
            @Override
            public Request authenticate(Proxy proxy, Response response) throws IOException {
                String credential = Credentials.basic(BldCnfg.REDDIT_CLIENT_ID, BldCnfg.REDDIT_PASS);
                return response.request().newBuilder().header("Authorization", credential).build();
            }

            @Override
            public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
                return null;
            }
        });
        return new OkClient(okHttpClient);
    }


    /*get the oAuth2 token*/
    public static RequestInterceptor getRequestInerceptorToken() {
        RequestInterceptor rqInter = new RequestInterceptor() {
            @Override
            public void intercept(RequestFacade request) {
                request.addHeader("Accept", "application/json");
                String token = "";
                // have some cache mechanism; it can be SharedPreferences 
                // I use SnappyDB; for reference see: http://is.gd/PF7tZT
                if (ValidationUtil.validateCache()) {
                    try {
                        if (CacheController.getInstance().getDB().exists(Static.ACCESS_TOKEN_KEY)) {
                            token = CacheController.getInstance().getDB().get(Static.ACCESS_TOKEN_KEY);
                        } else {
                          //handle this any way you think is appropriate 
                        }
                    } catch (SnappydbException e) {
                        e.printStackTrace();
                        LogUtil.dLog(Static.DB_TAG, "getRequestInterceptorToken() | error = " + e.getMessage());
                    }
                }
                request.addHeader("Authorization", token);
            }
        };
        return rqInter;
    }

}

 

I suggest you declare your constants in a separate classes

 

public class Urlz {

    //RETROFIT
    public static final String BASE_REDDIT_URL = "https://www.reddit.com";
    public static final String BASE_O_REDDIT_URL = "https://oauth.reddit.com";
    public static final String SUBREDDIT_PATH = "/r/{subreddit}/.json";
    public static final String LIIMT = "limit";
    public static final String AFTER = "after";
    public static final String SUBREDDIT = "subreddit";



    public static final String REDDIT_OATH2_PATH = "/api/v1/access_token";
}


public class BldCnfg {


    public static final String REDDIT_CLIENT_ID = "gfsgsfhsfhCdk"; // you get this by registering your app on reddit
    public static final String REDDIT_PASS = "";
    public static final String BODY_PARAMS = "grant_type=https://oauth.reddit.com/grants/installed_client&device_id=";
}

 

Don’t forget to call the methods in your Activity/Fragment


RedditAPI.sRedditAuth().redditAuth(requestBody, new Callback<TokenResponse>() {
              @Override
                public void success(TokenResponse tokenResponse, Response response) {
                    LogUtil.dLog(Static.OATH_TAG, "oAuth() | YAY! :)");
                    //save the token
                    if (tokenResponse != null && ValidationUtil.validateCache()) {
                        try {
                            if (tokenResponse.getAccessToken() != null) {
                                CacheController.getInstance().getDB().put(Static.ACCESS_TOKEN_KEY, tokenResponse.getTokenType() + " " + tokenResponse.getAccessToken());
                                LogUtil.dLog(Static.OATH_TAG, "oAuth() | access_token = " + tokenResponse.getAccessToken());
                            }
                        } catch (SnappydbException e) {
                            e.printStackTrace();
                            LogUtil.dLog(Static.DB_TAG, "oAuth() | error = " + e.getMessage());
                        }
                    }

                    //do something
                }

                @Override
                public void failure(RetrofitError error) {
                    LogUtil.dLog(Static.OATH_TAG, "oAuth() | NOOOOOoooooo.... :(");
                }
            });
            
        
    
    RedditAPI.sSubRedditContent().getSubredditContent(subreddit, limit, after, new Callback<SubredditContentResult>() {
            @Override
            public void success(SubredditContentResult subResult, Response response) {
                LogUtil.dLog("", "");
                //do something with the result 
            }

            @Override
            public void failure(RetrofitError error) {
                LogUtil.dLog("", "");

                //manage with message or something
            }
        });    

 

That’s it.

Posted in Android, AndroidDev, snippet, tutorial | Leave a comment

Google Play Store copyright issues and how to avoid them

Google started to be more rigorous when it comes down to the apps developers upload to their Play Store (official policy). How I found about this? By uploading an update to an existing app that I’ve put on the store year ago.

Namely, the application that I’ve uploaded to my Dev account is an application that is appropriate mobile version of an exiting Macedonian sport news site called Derbi (eng. Derby). The reason why I was doing the update was to implement some new stuff from the L and make my code GitHub presentable. So, I rewritten most of the code and implement some new libraries. When it came down to design, I’ve decided to go material with the launcher logo and with icons8 for some of the other icons.

I’ve uploaded and changes the screenshots and expected to have the update no longer than 6 hours after the upload. I checked my mail and saw this:

google_play_rejection

My app was rejected because of violation of the intellectual property and impersonation or deceptive behavior provisions of the Content Policy. I wanted to get to the bottom of this issues since the mail had a pretty serious tone of terminating my account, so the first thing I did was to ask icons8 if I’ve referenced the use of there icons properly. I described the case and they answered positively.

Next thing I did was turning to google for assistance and asking them to point out what is causing this unwanted issue. Form what I gather form the Internet, human from Google answering to this kind of issues is (almost) impossible. So, it was me against this new copyright infringement detecting bot. I came across this article and I started to doubt that it might have something to do with the screen shots and/or new material logo. I presumed that the bot that is doing the image analysis runs on similar algorithm as the search in images.google.com and I’ve put the old logo and new logo(s) to the test:

Old logo:

old_icon_similar_icons

New logo 1:

similar_images

New logo 2:

derbi_test

So it turns out the bot sees the new logo as very similar to the one of Facebook (go figure, right?). I doubt that this is the trigger. Then I did the same with the screenshots and it turns out it recognizes images in them and it references them, I presume in similar fashion as shown in the images below:

  jummy   warriors_screenshot

After this I turned to r/androiddev for help. I wrote this and got really insightful answers. Based on that, I’ve removed all the screenshots, left only two (blurred image instead of the actual one) and reverted to the old, non-material logo. The result is that the app was accepted.

As a lesson from this: When you upload screenshots always run them on images.google.com to see if they are referenced somewhere else. If you want to play it safe upload only images that you create.
And yeah, google might not let you go material, ‘cuz it might look like Facebook.

You can download the app here, and see the code on GitHub here.

 

Posted in Android, AndroidDev | Tagged , , | 1 Comment

All Gapps in one package

As an Android Developer you need to test your applications on various Android versions and screen resolutions. In order to do that, by far the best emulator around is Genymotion. If you’re not using it, you need to change that here. It presents the developer with various real life devices, but there is one thing though, they don’t come with pre-installed gapps (how to install them).  Google Gapps (or Google Apps as they are meant) are the proprietary applications by Google and need to be installed on the emulated device if you’re testing application that utilizes google services (notifications, youtube, maps, etc) in some way.

There are many sources that provide the appropriate gapps for appropriate Android version. Since I couldn’t find one download link to all gapps, I’ve gathered them in one folder and I’ve decided to share it with the world. You can download all gapps (up to the date of this blog post) here.

What the .rar contains:

gappsFolder

Posted in Android, AndroidDev | Tagged , , | Leave a comment

Adjust RecyclerView item height (example)

While playing around with RecyclerView I found that there was no easy method to determine proportional height (with the height of the screen) of the list items. After trying various approaches I found that the best way to achieve this and the way that worked the best for the application that I was doing at the moment, was determining height of the item of the RecyclerView in the adapter.

I’ve written simple utility function that determines the height of the screen and then I have predefined ratio (that can be subject to change from cached value that is inputted by the user at some point).
Here is the method:


public static int containerHeight(MainActivity ba) {
        DisplayMetrics dm = new DisplayMetrics();
        ba.getWindowManager().getDefaultDisplay().getMetrics(dm);
        
        LogUtil.dLog(Static.HEIGHT_TAG, "Screen Height of " + Build.MANUFACTURER + " " + Build.DEVICE + " "
                + Build.MODEL + " is " + Integer.toString(dm.heightPixels));

        //get predefined value
        double ratio = Static.PIC_RATIO_VALUE;

        //check if there is cached value; using SnappyDB
        try {
            if (ba.getDB() != null && ba.getDB().isOpen() && ba.getDB().exists(Static.PIC_RATIO))
                ratio = ba.getDB().getDouble(Static.PIC_RATIO);
        } catch (SnappydbException e) {
            e.printStackTrace();
            LogUtil.dLog(Static.DB_TAG, "SnappydbException | couldn't get Static.PIC_RATIO");
        }

        return (int) (dm.heightPixels / ratio);
    }

The method is applied in the adapter (in the onBindViewHolder method):


public class ItemsRecyclerAdapter extends RecyclerView.Adapter<ItemsRecyclerAdapter.SimpleItemViewHolder> {


    private ArrayList<SubRedditItem> mSubredditItems;
    private Context mCntx;
    OnItemClickListener mItemClickListener; //in order to enable onItemClickListener

    public class SimpleItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        @Override
        public void onClick(View v) {
            if (mItemClickListener != null) {
                mItemClickListener.onItemClick(v, getAdapterPosition()); //getPosition());
            }
        }

        TextView labelAndText;
        ImageView pic;
        RelativeLayout container;

        public SimpleItemViewHolder(View itemView) {
            super(itemView);

            labelAndText = (TextView) itemView.findViewById(R.id.txtItemTitle);
            pic = (ImageView) itemView.findViewById(R.id.imgItem);
            container = (RelativeLayout) itemView.findViewById(R.id.rlContainer);


        }
    }

    public ItemsRecyclerAdapter(Context c, ArrayList<SubRedditItem> si) {
        this.mSubredditItems = si;
        this.mCntx = c;
    }


    @Override
    public SimpleItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(mCntx).inflate(R.layout.recycler_item, parent, false);
        return new SimpleItemViewHolder(itemView);


    }

    @Override
    public void onBindViewHolder(SimpleItemViewHolder holder, int position) {

        if (holder.labelAndText != null && holder.pic != null && mSubredditItems.get(position) != null) {

            //set the title
            if (mSubredditItems.get(position).getTitle() != null)
                holder.labelAndText.setText(mSubredditItems.get(position).getTitle());

            //load image with picasso
            if (mSubredditItems.get(position).getUrl() != null)
               Picasso.with(mCntx).load(mSubredditItems.get(position).getUrl()).into(holder.pic);
            //holder._pic.setImage(ImageSource.uri(mSubredditItems.get(position).getUrl()));

            //set height in proportion to screen size
            int proportionalHeight = UIUtil.containerHeight((MainActivity) mCntx);
            TableRow.LayoutParams params = new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, proportionalHeight); // (width, height)
            holder.container.setLayoutParams(params);

        }

    }


    @Override
    public int getItemCount() {
        //if (recentPost != null && recentPost.getPosts() != null)

        return mSubredditItems.size();
    }


    public interface OnItemClickListener {
        public void onItemClick(View view, int position);
    }

    //to be added if more suitable
    public void SetOnItemClickListener(final OnItemClickListener mItemClickListener) {
        this.mItemClickListener = mItemClickListener;
    }
}


The item layout is defined in the following manner:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:id="@+id/rlContainer"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/imgItem"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/ChocolateTransparent"
        android:layout_alignParentBottom="true">

        <TextView
            android:id="@+id/txtItemTitle"
            android:text="@string/testText"
            android:textSize="16sp"
            android:textStyle="bold"
            android:paddingTop="20dp"
            android:paddingBottom="20dp"
            android:paddingRight="10dp"
            android:paddingLeft="10dp"
            android:textColor="@color/White"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </RelativeLayout>


</RelativeLayout>

The ratio can also be determined in correlation to the downloaded image dimensions (landscape/portrait), something that will yield additional changes

The example application is actually a reddit client (feed fetching interaction is managed with rest2mobile) that displays graphic submittions (images) and respective submittion title. When the user clicks on settings it can change the ratio of the items in the RecyclerView and change the subreddit.

Screenshots:

1rvt 2rvt4rvt

The code of the application can be found on GitHub.

Posted in Android, AndroidDev | Tagged , , | 1 Comment

Rest2Mobile Android example using Reddit feed

In the previous post I wore about Retrofit, how to use it and possible issues with SSL connection (and how to tackle them).

This post is dedicated to relatively new and exciting Android Studio plugin that generates Android REST client and appropriate Model of the JSON response. The plugin that I’ve been playing with is called Magnet rest2mobile and form what I’ve got chance to test it will be a serious threat to existing android REST client libs like Retrofit. You can download the tool and find installation screen-shot-type manual here. Even though, it can be installed on Eclipse (personally haven’t tried that, just going along with Magnet’s claim), it’s first and foremost created to run on Android Studio. res2mobileLogo What Rest2Mobile does, is that it turns actual REST requests and responses into source code (Objective-C, Android, and JavaScript) that you can call from your mobile app. The source code automatically handles: – server connections – marshalling and unmarshalling JSON data – JSON data conversion into the native object types (no need to use POJO generators for the model/response object, classes are auto generated) Given REST request/response examples, rest2mobile can infer the entire object model for your requests and responses. The IDE plugins and CLI just ask you to specify the GET/POST/PUT/DELETE REST requests and responses and automatically generates the method and types required to invoke the REST service from your Mobile App.

So, lets take this to the test. I chose to use Reddit feed (in JSON format), mainly because of its complexity- if the tool can handle this, most likely will be able to handle anything. In the images below is the Reddit feed that can be accessed via http://www.reddit.com/.json.

complexJSON

If you want to access particular subreddit feed, you do it by http://www.reddit.com/r/[subreddit]/.json (1). It’s more than likely that you’ll need more than the first 25 results that will be fetched so if you want to scroll through the pages the call should be modified in the following format: httр://reddit.com/r/[subreddit]/.[rѕѕ/jѕon] ? limit=[limit]&after=[after] (2). For the purpose of simplicity I’ll present an example that utilizes the format (1). Needless to say, the first thing you need to do is to install the plugin (link provided in the first section of this blog post). After you do that, form the Studio’s status bar you choose freshly installed R2M plugin and you click on ‘Add REST API’. As a result of that action you’re presented with the following window:

setTheMethod

Populate the necessary fields, generated Class name and package to which the class will belong to. Define the name of the method that will be created and also the API call, path and parameters (in the case of (2) this needs to be done). Click on ‘Test API’ to have a preview and if everything runs smoothly, press ‘Generate’. After this action is completed, the result is shown in the image below:

projectStructure

As it can be seen, I added another wrapper class for more control over the API call. Also, worth mentioning is that with this automated generation, unit test is also generated under src/androidtest/java/. The RedditAPI class is defined in the following manner:

public class RedditAPI {

    /*
    * give the context form where you want to call the method and the subbredit you want the feed from
    * */

    public static void getSubRedditFeed(Context c, String subreddit) throws SchemaException {
        MagnetMobileClient magnetClient = MagnetMobileClient.getInstance(c);
        RedditMagnetAPIFactory controllerFactory = new RedditMagnetAPIFactory(magnetClient);
        RedditMagnetAPI redditMagnetAPI = controllerFactory.obtainInstance();

        Call&lt;SubredditFeedResult&gt; callObject = redditMagnetAPI.getSubredditFeed(subreddit, null);
        //auto-generated object
        SubredditFeedResult result = null;
        try {
            result = callObject.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        //cache the result or manipulate it in meaningful manner
        // i.e. make a list of objects that have title, picURL and karma (Upvotes - Downvotes)
        /*
        for each children i:
        result.getData().getChildren().get(i).getData().getTitle();
        result.getData().getChildren().get(i).getData().getUrl();
        result.getData().getChildren().get(i).getData().getUps();
        result.getData().getChildren().get(i).getData().getDowns();
        */
        result.getData();
    }
}

The method defined in the code above can be called in Activity/Fragment and is as simple as this:

public class MyActivity extends Activity {



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        try {
            RedditAPI.getSubRedditFeed(this, &quot;funny&quot;); // &quot;gonewild&quot; optimal, though
        } catch (SchemaException e) {
            e.printStackTrace();
        }

    }

//....
}

The result form the call: recievedData And that’s it. As you can see it’s pretty simple to generate and handle API call. Highly usable and time saving tool. The cons maybe the (lack of) flexibility when it comes to making changes after a method (API call) is generated and error/exception handling (haven’t tested it with SSL connection).

All in all, it definitely looks like something I’ll  use in future projects.

For more info see this video and this example.

Posted in Android, AndroidDev | Tagged | 4 Comments

SSL, Android and Retrofit. Some frustration might occur

SSL is short for Secure Sockets Layer, a protocol developed by Netscape for transmitting private documents and sensitive data via the Internet. SSL uses a cryptographic system that uses two keys to encrypt data −a public key known to everyone and a private or secret key known only to the recipient of the message.

The protocol is frequently used as a way for the application (iOS or Android) to communicate with server (presumably) via RESTful API. The Android end point (client) can be build using various libs that are out there, but arguably the best one is Retrofit. The dictionary definition of retrofitting goes as follows: retrofitting is act of adding a component or accessory to something that did not have it when it was manufactured, which is exactly what this lib does when it comes to client end implementation of and communication with RESTful services. The minimum required Android version is 2.2.

Before you start make sure you have the latest Retrofit lib imported in your project alongside with the corresponding libs (version number is very important) that enable the communication.

  • retrofit-1.7.1
  • okhttp-2.0.0
  • okio-1.0.0
  • okhttp-urlconnection-2.0.0

Simple example of Restrofit implementation is shown below:

 
public class RfitAPI {

// declaration of service interfaces
private static SomeInterface sSomeService;

public interface SomeInterface {
        @GET("/somepath/some_service")
        void someInterfaceMethod(@Query("param_name") String code,
                Callback<SomeResponseObject> callback);

    }

//the adapter 
public static SomeInterface smIntfMthd() {
        if (sSomeService == null) {
            
            RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(
                    "https://somevendor.com").build();
            sSomeService = restAdapter
                    .create(SomeInterface.class);
        }

        return sSomeService;
    }



}
 
public void callSomeMethod(String code) {

                //calling https://somevendor.com/somepath/some_service?param_name=code

        RfitAPI.smIntfMthd().someInterfaceMethod(code, new Callback<SomeResponseObject>(){

            @Override
            public void failure(RetrofitError errRT) {
                //handle error
                
            }

            @Override
            public void success(SomeResponseObject arg0, Response arg1) {
                //handle success
                
            }
            
        });
    }

If you’re running on Android 4+, this code should be sufficient and everything should run smoothly. Yet, there are a couple of things that can go wrong. You might get certification error, or you might not be able to access the service. Most likely lower android versions – 2.3 in my experience, will present some kind of exceptions, but also I’ve seen some devices that run on 4.1.1 to have issues with the certification process.

The first thing you should do is to check the date on your phone (yes, I know you’re smarter than that, but please check). Your device might got restarted, it’s a test device that you only use in the office or whatever may the reason be, there is possibility that the date is not the current one and it’s the reason you’re getting the error.

Second thing when you’re resolving certification issues in this kind of setting is to do a slight modification of the above presented code. It goes as follows:

 
//the adapter 
public static SomeInterface smIntfMthd() {
        if (sSomeService == null) {
                        //adding client instance 
            OkClient client = new OkClient();
            RestAdapter restAdapter = new RestAdapter.Builder().setClient(client).setEndpoint(
                    "https://somevendor.com").build();
            sSomeService = restAdapter
                    .create(SomeInterface.class);
        }

        return sSomeService;
    }

Third tackle of the issue, most likely with SSLHandshakeException, is here or here.

Forth and last and least recommendable way of dealing with SSL problems is based on surpassing the certification process. In order to surpass the handshake process you need to add custom client, and that’s done in the following manner:

 
public static OkHttpClient getUnsafeOkHttpClient() {
        
            try {
                // Create a trust manager that does not validate certificate chains
                final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(
                            java.security.cert.X509Certificate[] chain,
                            String authType) throws CertificateException {
                    }

                    @Override
                    public void checkServerTrusted(
                            java.security.cert.X509Certificate[] chain,
                            String authType) throws CertificateException {
                    }

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                } };

                // Install the all-trusting trust manager
                final SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, trustAllCerts,
                        new java.security.SecureRandom());
                // Create an ssl socket factory with our all-trusting manager
                final SSLSocketFactory sslSocketFactory = sslContext
                        .getSocketFactory();

                OkHttpClient okHttpClient = new OkHttpClient();
                okHttpClient.setSslSocketFactory(sslSocketFactory);
                okHttpClient.setHostnameVerifier(new HostnameVerifier() {
                    
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        // TODO Auto-generated method stub
                        if (hostname.еquals("https://somevendor.com"))
                        return true;
                        else 
                            return false;
                    }
                });

                return okHttpClient;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        
    }
    
    public static OkClient  getOkClient (){
        OkHttpClient client1 = new OkHttpClient();
        client1 = getUnsafeOkHttpClient();
        OkClient _client = new OkClient(client1);
        return _client;
    }

Then change the adapter:

 
public static SomeInterface smIntfMthd() {
        if (sSomeService == null) {
                        //adding unsafe/custom client 
            OkClient client = getOkClient();
            RestAdapter restAdapter = new RestAdapter.Builder().setClient(client).setEndpoint(
                    "https://somevendor.com").build();
            sSomeService = restAdapter
                    .create(SomeInterface.class);
        }

        return sSomeService;
    }

Since this increases the vulnerability of implemented solution, do this only if you know what you’re doing!

On a closing note, if your application includes downloading of images, most likely you’ll have to implement same solution on some level. For image download use Glide or Picasa and add custom clients to handle the exceptions. Check how that’s done, here.

Posted in Android, AndroidDev, tutorial | Tagged , , | 6 Comments

2014 Albums Worth Listening To

What I was listening to in the past year. [Spoiler alert] It’s mostly hip hop.

Yes, in the time of this blog post “DJ Premier & Royce Da 5’9″ – PRhyme” is not officially out, but what I streamed form the album qualifies it to be in this grid.

Posted in Music | Tagged , , | Leave a comment

Using auto resize to fit EditText in Android

UPDATE: [I’ve created lib project on gitHub]

Having the need of auto resizable EditText usage in Android is certainly something you’ll come across creating interactive applications at some point. I came to that point and to me it seemed that somebody has to have developed this custom widget/library and put it somewhere (gitHub) for reutilization. Partially, I was right, but it turned out that most of the solutions that are out there, need(ed) fixing, changing and a lot of customization (which takes a lot of time and I don’t advise doing, because it can consume as much time as starting from scratch).

First of all, let me say that there’s no “EditText” auto resize widgets and all of them extend TextView, which won’t be an issue since EditText extends TextView, right? Wrong. And I’ll get back at this later. Some of the best/worth mentioning AutoResize libs/widgets:
1) AndroidDeveloperLB/AutoFitTextView
2) skimarxall/RealTextView (editText widget is included in the lib)
3) danclarke/AutoResizeTextView
4) lucamtudor/AutoResizeTextView
5) grantland/android-autofittextview

The stackoverflow question that tracks this issue of auto-resizing TextView (EditText) is here. It seems that android-developer has aggregated all the possible approaches, ideas and concepts in order to tackle this issue most efficiently (I suggest you read the post and go through it before you continue reading). As a result of this AndroidDeveloperLB/AutoFitTextView lib/widget was created and I’ll discuss only this solution since I think is the most complete. If you’re willing you can try the others, most of the things here should apply to them also.

You start off by changing the extend class from TextView to EditText. For those of you that are lazy to do this, here is copy-paste:

 

public class AutoResizeEditText extends EditText {
    private static final int NO_LINE_LIMIT = -1;
    private final RectF _availableSpaceRect = new RectF();
    private final SparseIntArray _textCachedSizes = new SparseIntArray();
    private final SizeTester _sizeTester;
    private float _maxTextSize;
    private float _spacingMult = 1.0f;
    private float _spacingAdd = 0.0f;
    private float _minTextSize;
    private int _widthLimit;
    private int _maxLines;
    private boolean _enableSizeCache = true;
    private boolean _initiallized = false;
    private TextPaint paint;

    private interface SizeTester {
        /**
         * AutoResizeEditText
         *
         * @param suggestedSize
         *            Size of text to be tested
         * @param availableSpace
         *            available space in which text must fit
         * @return an integer < 0 if after applying {@code suggestedSize} to
         *         text, it takes less space than {@code availableSpace}, > 0
         *         otherwise
         */
        public int onTestSize(int suggestedSize, RectF availableSpace);
    }

    public AutoResizeEditText(final Context context) {
        this(context, null, 0);
    }

    public AutoResizeEditText(final Context context, final AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AutoResizeEditText(final Context context, final AttributeSet attrs,
            final int defStyle) {
        super(context, attrs, defStyle);
        // using the minimal recommended font size
        _minTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                12, getResources().getDisplayMetrics());
        _maxTextSize = getTextSize();
        if (_maxLines == 0)
            // no value was assigned during construction
            _maxLines = NO_LINE_LIMIT;
        // prepare size tester:
        _sizeTester = new SizeTester() {
            final RectF textRect = new RectF();

            @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
            @Override
            public int onTestSize(final int suggestedSize,
                    final RectF availableSPace) {
                paint.setTextSize(suggestedSize);
                final String text = getText().toString();
                final boolean singleline = getMaxLines() == 1;
                if (singleline) {
                    textRect.bottom = paint.getFontSpacing();
                    textRect.right = paint.measureText(text);
                } else {
                    final StaticLayout layout = new StaticLayout(text, paint,
                            _widthLimit, Alignment.ALIGN_NORMAL, _spacingMult,
                            _spacingAdd, true);
                    // return early if we have more lines
                    Log.d("NLN", "Current Lines = "+Integer.toString(layout.getLineCount()));
                    Log.d("NLN", "Max Lines = "+Integer.toString(getMaxLines()));
                    if (getMaxLines() != NO_LINE_LIMIT
                            && layout.getLineCount() > getMaxLines())
                        return 1;
                    textRect.bottom = layout.getHeight();
                    int maxWidth = -1;
                    for (int i = 0; i < layout.getLineCount(); i++)
                        if (maxWidth < layout.getLineWidth(i))
                            maxWidth = (int) layout.getLineWidth(i);
                    textRect.right = maxWidth;
                }
                textRect.offsetTo(0, 0);
                if (availableSPace.contains(textRect))
                    // may be too small, don't worry we will find the best match
                    return -1;
                // else, too big
                return 1;
            }
        };
        _initiallized = true;
    }

    @Override
    public void setTypeface(final Typeface tf) {
        if (paint == null)
            paint = new TextPaint(getPaint());
        paint.setTypeface(tf);
        super.setTypeface(tf);
    }

    @Override
    public void setTextSize(final float size) {
        _maxTextSize = size;
        _textCachedSizes.clear();
        adjustTextSize();
    }

    @Override
    public void setMaxLines(final int maxlines) {
        super.setMaxLines(maxlines);
        _maxLines = maxlines;
        reAdjust();
    }

    @Override
    public int getMaxLines() {
        return _maxLines;
    }

    @Override
    public void setSingleLine() {
        super.setSingleLine();
        _maxLines = 1;
        reAdjust();
    }

    @Override
    public void setSingleLine(final boolean singleLine) {
        super.setSingleLine(singleLine);
        if (singleLine)
            _maxLines = 1;
        else
            _maxLines = NO_LINE_LIMIT;
        reAdjust();
    }

    @Override
    public void setLines(final int lines) {
        super.setLines(lines);
        _maxLines = lines;
        reAdjust();
    }

    @Override
    public void setTextSize(final int unit, final float size) {
        final Context c = getContext();
        Resources r;
        if (c == null)
            r = Resources.getSystem();
        else
            r = c.getResources();
        _maxTextSize = TypedValue.applyDimension(unit, size,
                r.getDisplayMetrics());
        _textCachedSizes.clear();
        adjustTextSize();
    }

    @Override
    public void setLineSpacing(final float add, final float mult) {
        super.setLineSpacing(add, mult);
        _spacingMult = mult;
        _spacingAdd = add;
    }

    /**
     * Set the lower text size limit and invalidate the view
     *
     * @param 

     */
    public void setMinTextSize(final float minTextSize) {
        _minTextSize = minTextSize;
        reAdjust();
    }

    private void reAdjust() {
        adjustTextSize();
    }

    private void adjustTextSize() {
        if (!_initiallized)
            return;
        final int startSize = (int) _minTextSize;
        final int heightLimit = getMeasuredHeight()
                - getCompoundPaddingBottom() - getCompoundPaddingTop();
        _widthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
                - getCompoundPaddingRight();
        if (_widthLimit <= 0)
            return;
        _availableSpaceRect.right = _widthLimit;
        _availableSpaceRect.bottom = heightLimit;
        super.setTextSize(
                TypedValue.COMPLEX_UNIT_PX,
                efficientTextSizeSearch(startSize, (int) _maxTextSize,
                        _sizeTester, _availableSpaceRect));
    }

    /**
     * Enables or disables size caching, enabling it will improve performance
     * where you are animating a value inside TextView. This stores the font
     * size against getText().length() Be careful though while enabling it as 0
     * takes more space than 1 on some fonts and so on.
     *
     * @param enable
     *            enable font size caching
     */
    public void setEnableSizeCache(final boolean enable) {
        _enableSizeCache = enable;
        _textCachedSizes.clear();
        adjustTextSize();
    }

    private int efficientTextSizeSearch(final int start, final int end,
            final SizeTester sizeTester, final RectF availableSpace) {
        if (!_enableSizeCache)
            return binarySearch(start, end, sizeTester, availableSpace);
        final String text = getText().toString();
        final int key = text == null ? 0 : text.length();
        int size = _textCachedSizes.get(key);
        if (size != 0)
            return size;
        size = binarySearch(start, end, sizeTester, availableSpace);
        _textCachedSizes.put(key, size);
        return size;
    }

    private int binarySearch(final int start, final int end,
            final SizeTester sizeTester, final RectF availableSpace) {
        int lastBest = start;
        int lo = start;
        int hi = end - 1;
        int mid = 0;
        while (lo <= hi) {
            mid = lo + hi >>> 1;
            final int midValCmp = sizeTester.onTestSize(mid, availableSpace);
            if (midValCmp < 0) {
                lastBest = lo;
                lo = mid + 1;
            } else if (midValCmp > 0) {
                hi = mid - 1;
                lastBest = hi;
            } else
                return mid;
        }
        // make sure to return last best
        // this is what should always be returned
        return lastBest;
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        super.onTextChanged(text, start, before, after);
        reAdjust();
    }

    @Override
    protected void onSizeChanged(final int width, final int height,
            final int oldwidth, final int oldheight) {
        _textCachedSizes.clear();
        super.onSizeChanged(width, height, oldwidth, oldheight);
        if (width != oldwidth || height != oldheight)
            reAdjust();
    }
}

 

Needless to say, you need to import this in your project. Put the newly imported widget in xml (add values for maxLines, maxLength in order to make the fitting more precise) and reference it in your Activity/Fragment as follows:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:real="http://schemas.android.com/apk/res-auto"
    android:id="@+id/rlRoot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.testapplication.MainActivity" >

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="500dp"
        android:layout_height="333dp"
        android:layout_centerInParent="true"
        android:background="#ffff0000" >

        <com.example.autofit.et.AutoResizeEditText
            android:id="@+id/rET"
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            android:background="#FF00FF"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center_vertical|center_horizontal"
            android:hint="HINT"
            android:isScrollContainer="false"
            android:inputType="textMultiLine|textNoSuggestions"
            android:maxLength="240"
            android:textColor="#000000"
            android:textSize="90sp" />
    </FrameLayout>

</RelativeLayout>

 

public class MainActivity extends Activity {

    private AutoResizeEditText mAutoResizeEditText;
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
mAutoResizeEditText = (AutoResizeEditText) findViewById(R.id.rET);
        
                
        
    }
    
    @Override
    protected void onResume() {
        super.onResume();

    }

 

When you run the application you might not be able to click on the EditText, i.e. it behaves as TextView. Now coming back to the part that I mentioned in the beginning, in order to fix this, you MUST provide this lines in your xml declaration of the file:

android:focusable="true"
android:focusableInTouchMode="true"

It seems that we’re done and everything should work fine, but this s*it happens:doesntWork1 doesntWork2doesntWork3doesntWork4

Text is not in bounds and pressing new line seems to distort the expected behavior. There are a lot of possible limitations in order to set bounds (maxLines, maxLength, maxTextSize, maxHeight) to the text and stop this aberrant behavior, yet most of them doesn’t seem to work. After long hours spent on this and a lot of cup of coffees, this is what I think works best. The CRUCIAL FIX is anchoring your logic on maxHeight by adding the following lines after the widget reference:

mAutoResizeEditText = (AutoResizeEditText) findViewById(R.id.rET);
        mAutoResizeEditText.setEnabled(true);
        mAutoResizeEditText.setFocusableInTouchMode(true);
        mAutoResizeEditText.setFocusable(true);
        mAutoResizeEditText.setEnableSizeCache(false);
        mAutoResizeEditText.setMovementMethod(null);
        // can be added after layout inflation; it doesn't have to be fixed
        // value
        mAutoResizeEditText.setMaxHeight(330);

 

BONUS. To make things look slick (soft keys disappear if you tap outside the EditText container; also if font size is too small due to a lot of empty new lines, the EditText string is trimmed) add this code:

@Override
    protected void onResume() {
        super.onResume();
        setupUI(findViewById(R.id.rlRoot), mAutoResizeEditText);

    }

 

Where setupUI is defined in the following manner:

public void setupUI(View view, final AutoResizeEditText aText) {

        // if the view is not instance of AutoResizeEditText
        // i.e. if the user taps outside of the box
        if (!(view instanceof AutoResizeEditText)) {

            view.setOnTouchListener(new OnTouchListener() {
                
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                     hideSoftKeyboard();

                        Log.d("TXTS",
                                "Text Size = "
                                        + aText.getTextSize());
                        if (aText.getTextSize() < 50f) {
                            // you can define your minSize, in this case is 50f
                            // trim all the new lines and set the text as it was
                            // before
                            aText.setText(aText.getText().toString().replaceAll("(?m)^[ \t]*\r?\n", ""));
                        }

                        return false;
                }
            });
        }

        // If a layout container, iterate over children and seed recursion.
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                View innerView = ((ViewGroup) view).getChildAt(i);
                setupUI(innerView, aText);
            }
        }
    }

    public void hideSoftKeyboard() {
        InputMethodManager inputMethodManager = (InputMethodManager) this
                .getSystemService(Activity.INPUT_METHOD_SERVICE);
        if (this.getCurrentFocus() != null
                && this.getCurrentFocus().getWindowToken() != null)
            inputMethodManager.hideSoftInputFromWindow(this
                    .getCurrentFocus().getWindowToken(), 0);
    }

 

Result:

works1works2works3

If you have any questions or suggestions, feel free to use the comments section of the blog post or hit me up.
P.S. The videos were recorder using Recordable.

Posted in AndroidDev, snippet, tutorial | Tagged , , , | 15 Comments