Saturday, December 27, 2014

Converting app ideas into reality


Recently I came across this post. It describes how to start working with new app ideas and make them market ready. It also mentions some tools to analyze competition of existing apps, prepare wireframes, server support.
I hope you will find it useful too.
For wireframes more free tools are available. These are mentioned in here.

Saturday, November 1, 2014

Reducing memory consumption (Part 2)

As we have seen in earlier post first method to reduce memory was reducing font loading. This is described here.
Next method is to use weak references.

2. Making use of weak references
Our application uses observer model to notify data availability. Here a EventNotifier class is used to register listeners for the event. As soon as the event occurs, it fires the event with EventId and EventObject.

Some activities use data that is fetched from server. In order to get this event activity registers itself with the EventNotifier. There is a communicator thread, which downloads data, and calls method public int eventNotify(int eventType, Object eventObject) of EventNotifier. Now this method finds out registered listeners and informs them about update.

Problem comes when Android decides to kill activity for reusing resources. In this case OnDestroy() method may not get called. OnDestroy() handles unregistration activity from EventNotifier. Now as the EventNotifier still holds reference to this activity, it leaks the large amount of memory required by activity, views shown in it, etc.

This can be resolved by marking the ativity reference as weakreference.

public void registerListener(WeakReference < IEventListener > eventListener, int listenerPriority)

WeakReference allows GC to garbage collect activity instance even if, EventNotifier holds WeakReference of it. With strong reference (created by code "EventListener eventListener = this") GC can not garbage collect the activity instance.

3. Making handlers static and final

When a handler is instantiated inside an activity lifecycle, it holds reference to entire activity. If the activity is destroyed but the handler is still in use, GC can not collect the activity as handler is still holding the reference to it. e.g.

protected void onCreate(Bundle savedInstanceState) {
        ...
    Handler handler = new Handler();
    handler.post(new Runnable() {

        @
        Override
        public void run() {
            // some stuff running on UI thread
        }
    });
        ...
}


Here handler action is created as inner class. Each inner class in Java holds reference to outer class. This handler instance holds instance of Activity which leaks memory. To avoid this make static final instance of handler.

private final MyHandler mHandler = new MyHandler(this);

/**
 * Instances of anonymous classes do not hold an implicit
 * reference to their outer class when they are "static".
 */
private static final Runnable sRunnable = new Runnable() {@
    Override
    public void run() {}
};

@
Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 60 * 10 * 1000);
}

Reducing memory consumption (Part 1)

Recently our app faced lot of issues because of huge memory consumption. We resolved these issues using following techniques:
1. Reducing font instances
2. Making use of weak references
3. Making handlers static and final

In this post we will see first approach. Second approach will be covered in next post.
1. Reducing font instances
We used to have a custom text view. This text view uses a font provided by application. Code below shows how it used to load.

public boolean setCustomFont(Context ctx, String asset) {
    Typeface tf = null;
    try {
        tf = Typeface.createFromAsset(ctx.getAssets(), "fonts/" + asset.trim());
    } catch (Exception e) {
        Log.e(TAG, "Could not get typeface: " + e.getMessage() + "\n value of asset string " + asset);
        return false;
    }

    setTypeface(tf);
    return true;
}

After using memory profiling tool of android, we came to know that each time this activity is opened memory usage increases. This tool shown the resource held as well.
adb shell dumpsys meminfo com.example.prj
Next step was to reduce the repeated loading of font.
private static Typeface typeFace = null;
public static Typeface getTypeFace(Context ctx, String asset) {
    if (typeFace == null)
        typeFace = Typeface.createFromAsset(ctx.getAssets(), "fonts/" + asset.trim());
    return typeFace;
}
Code above makes the object of type face only once. If it already exists it won't be created again and old object will be used. Before making this change, whenever activity using this TextView was opened, it used to increase memory consumption by 4-5Mb. The size of font file was 0.5 Mb and it was getting loaded 8-10 times on this activity. After making this change, font is loaded only once and takes about 0.5Mb memory.

Saturday, May 17, 2014

Pager Tabs with Icon and text which appears to have fixed width.

We are going to create pager tabs, which unlike default library will be able to show more than 3 tabs in a screen. All the tabs will close to each other.


PagerTab
 
To achieve this purpose we shall need a library located at http://viewpagerindicator.com/. Download the library project, named as JakeWharton-Android-ViewPagerIndicator-2.4.1-0-g8cd549f.zip. It includes a library and sample project. We need library for our app. If you are curious, you may check the sample project as well. Import the library project in eclipse.
 
Create a new Android project and open its properties. Add library project in this project using Android section in properties. Clean library and android project. Add the following in your activity_layout.xml.

<com.viewpagerindicator.TabPageIndicator

                android:id="@+id/indicator"

                style="@style/CustomTabPageIndicator"

                android:layout_width="fill_parent"

                android:layout_height="30dp"

                 android:visibility="gone"/>

<android.support.v4.view.ViewPager

            xmlns:tools="http://schemas.android.com/tools"

            android:id="@+id/pager"

            android:layout_width="match_parent"

            android:layout_height="0dp"

            android:layout_weight="1"

            tools:context=".VMEBaseClass" >

        </android.support.v4.view.ViewPager>


Whereas style.xml will contain:

<style name="CustomTabPageIndicator" parent="Widget.TabPageIndicator">

        <!-- <item name="android:background">@drawable/custom_tab_indicator</item> -->

        <item name="android:textAppearance">@style/CustomTabPageIndicator.Text</item>

        <item name="android:textColor">#cccccc</item>

        <item name="android:textSize">12sp</item>

        <!-- <item name="android:divider">@drawable/vertical_line</item> -->

        <!-- @drawable/custom_tab_indicator_divider</item> -->

        <!-- <item name="android:dividerPadding">10dp</item>

        <item name="android:showDividers">middle</item> -->

        <item name="android:paddingLeft">0dp</item>

        <item name="android:paddingRight">0dp</item>

        <item name="android:fadingEdge">horizontal</item>

        <item name="android:paddingTop">1dp</item>

        <item name="android:paddingBottom">0dp</item>

        <item name="android:fadingEdgeLength">8dp</item>

        <item name="android:layout_marginTop">5dp</item>

        <item name="android:layout_marginBottom">5dp</item>

    </style>


In your FragmentActivity's onCreate(), add the following code.

BasePagerAdapter mSectionsPagerAdapter = new BasePagerAdapter( getSupportFragmentManager( ) );

        

        // Set up the ViewPager with the sections adapter.

        mViewPager = (ViewPager) findViewById( R.id.pager );

        mViewPager.setAdapter( mSectionsPagerAdapter );

        

        TabPageIndicator indicator = (TabPageIndicator) findViewById( R.id.indicator );

        indicator.setVisibility( View.VISIBLE );

        indicator.setViewPager( mViewPager );


Implement the IconPagerAdapter for showing icons and background in both normal and selected states. In this sample we will support 4 pages.

public class BasePagerAdapter extends FragmentPagerAdapter implements IconPagerAdapter {

        

        public VMEBasePagerAdapter( FragmentManager fm ) {

            super( fm );

        }

        

        @Override

        public Fragment getItem( int position ) {

            // getItem is called to instantiate the fragment for the given page.

                       

            if ( position == 0 ) {

                Fragment fragment = new DownloadFragment();

                return fragment;

            } else if ( position == 1 ) {

                Fragment fragment = new FavoriteFragment( favoriteData);

                return fragment;

            } else if ( position == 2 ) {

                Fragment fragment = new FlashFragment();

                return fragment;

            } else if ( position == 3 ) {

                Fragment fragment = null;

                fragment = new DummyFragment();

                return fragment;

            } else {

                return null;

            }

        }

        

        @Override

        public int getCount( ) {

//Return the count of pages.

            return 4;

        }

        

        @Override

        public CharSequence getPageTitle( int position ) {

            switch ( position ) {

                case 0:

                    return getString( R.string.title_download );

                    

                case 1:

                    return getString( R.string.title_favorite );

                    

                case 2:

                    return getString( R.string.title_flash );

                    

                case 3:

                    return getString( R.string.title_dummy );

            }

            return null;

        }

        

        @Override

        public int getIconResIdNormal( int index ) {

switch ( index ) {

                case 0:

                    return R.drawable.ic_normal_download;

                    

                case 1:

                    return R.drawable.ic_normal_favorite;

                    

                case 2:

                    return R.drawable.ic_normal_flash;

                    

                case 3:

                    return R.drawable.ic_normal_dummy;

            }

            return 0;

        }

        

        @Override

        public int getIconResIdSelected( int index ) {

            switch ( index ) {

                case 0:

                    return R.drawable.ic_selected_download;

                    

                case 1:

                    return R.drawable.ic_selected_favorite;

                    

                case 2:

                    return R.drawable.ic_selected_flash;

                    

                case 3:

                    return R.drawable.ic_selected_dummy;

            }

            return 0;

        }

        

        @Override

        public int getBackgroundResIdSelected( int index ) {

            switch ( index ) {

                case 0:

                    return R.drawable.tab_left_pressed;

                case 1:

                case 2:

                    return R.drawable.tab_center_pressed;

                case 3:

                    return R.drawable.tab_right_pressed;

            }

            return 0;

        }

        

        @Override

        public int getBackgroundResIdNormal( int index ) {

            switch ( index ) {

                case 0:

                    return R.drawable.tab_left_normal;

                case 1:

                case 2:

                    return R.drawable.tab_center_normal;

                case 3:

                    return R.drawable.tab_right_normal;

            }

            return 0;

        }

    }


Now its time to modify the library project's TabPageIndicator.java like this.

public class TabPageIndicator extends HorizontalScrollView implements PageIndicator {
    /** Title text used when no title is provided by the adapter. */
    private static final CharSequence EMPTY_TITLE = "";
    //private Vector< TabView > tabViews = new Vector< TabPageIndicator.TabView >( );
    
    /**
     * Interface for a callback when the selected tab has been reselected.
     */
    public interface OnTabReselectedListener {
        /**
         * Callback when the selected tab has been reselected.
         * 
         * @param position
         *            Position of the current center item.
         */
        void onTabReselected( int position );
    }
    
    private Runnable mTabSelector;
    
    private final OnClickListener mTabClickListener = new OnClickListener( ) {
        public void onClick( View view ) {
            //TabView tabView = (TabView) view;
            final int oldSelected = mViewPager.getCurrentItem( );
            final int newSelected = (Integer)view.getTag( );
            mViewPager.setCurrentItem( newSelected );
            if ( oldSelected == newSelected && mTabReselectedListener != null ) {
                mTabReselectedListener.onTabReselected( newSelected );
            }
        }
    };
    
    private final IcsLinearLayout mTabLayout;
    
    private ViewPager mViewPager;
    private ViewPager.OnPageChangeListener mListener;
    
    private int mMaxTabWidth;
    private int mSelectedTabIndex;
    
    private OnTabReselectedListener mTabReselectedListener;
    
    public TabPageIndicator( Context context ) {
        this( context, null );
    }
    
    public TabPageIndicator( Context context, AttributeSet attrs ) {
        super( context, attrs );
        setHorizontalScrollBarEnabled( false );
        
        mTabLayout = new IcsLinearLayout( context, R.attr.vpiTabPageIndicatorStyle );
        addView( mTabLayout, new ViewGroup.LayoutParams( WRAP_CONTENT, MATCH_PARENT ) );
    }
    
    public void setOnTabReselectedListener( OnTabReselectedListener listener ) {
        mTabReselectedListener = listener;
    }
    
    @Override
    public void onMeasure( int widthMeasureSpec, int heightMeasureSpec ) {
        final int widthMode = MeasureSpec.getMode( widthMeasureSpec );
        final boolean lockedExpanded = widthMode == MeasureSpec.EXACTLY;
        setFillViewport( lockedExpanded );
        
        final int childCount = mTabLayout.getChildCount( );
        if ( childCount > 1 && ( widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST ) ) {
            if ( childCount > 2 ) {
                mMaxTabWidth = (int) ( MeasureSpec.getSize( widthMeasureSpec ) * 0.4f );
            } else {
                mMaxTabWidth = MeasureSpec.getSize( widthMeasureSpec ) / 2;
            }
        } else {
            mMaxTabWidth = -1;
        }
        
        final int oldWidth = getMeasuredWidth( );
        super.onMeasure( widthMeasureSpec, heightMeasureSpec );
        final int newWidth = getMeasuredWidth( );
        
        if ( lockedExpanded && oldWidth != newWidth ) {
            // Recenter the tab display if we're at a new (scrollable) size.
            setCurrentItem( mSelectedTabIndex );
        }
    }
    
    private void animateToTab( final int position ) {
        final View tabView = mTabLayout.getChildAt( position );
        if ( mTabSelector != null ) {
            removeCallbacks( mTabSelector );
        }
        mTabSelector = new Runnable( ) {
            public void run( ) {
                final int scrollPos = tabView.getLeft( ) - ( getWidth( ) - tabView.getWidth( ) ) / 2;
                smoothScrollTo( scrollPos, 0 );
                mTabSelector = null;
            }
        };
        post( mTabSelector );
    }
    
    @Override
    public void onAttachedToWindow( ) {
        super.onAttachedToWindow( );
        if ( mTabSelector != null ) {
            // Re-post the selector we saved
            post( mTabSelector );
        }
    }
    
    @Override
    public void onDetachedFromWindow( ) {
        super.onDetachedFromWindow( );
        if ( mTabSelector != null ) {
            removeCallbacks( mTabSelector );
        }
    }
    
    private void addTab( int index, CharSequence text, int iconResId ) {
        LayoutInflater inflater = (LayoutInflater)getContext( ).getSystemService( Context.LAYOUT_INFLATER_SERVICE );
        View view = inflater.inflate( R.layout.tab_layout, null);
        ImageView imageView = (ImageView)view.findViewById( R.id.ivIcon );
        TextView textView = (TextView)view.findViewById( R.id.tvTabText );
        imageView.setImageResource( iconResId );
        textView.setText( text );
        view.setOnClickListener( mTabClickListener );
        view.setTag( index );
        mTabLayout.addView( view, new LinearLayout.LayoutParams( 0, MATCH_PARENT, 1 ) );
        
        /*final TabView tabView = new TabView( getContext( ) );
        tabView.mIndex = index;
        tabView.setFocusable( true );
        tabView.setOnClickListener( mTabClickListener );
        tabView.setText( text );
        
        if ( iconResId != 0 ) {
            tabView.setCompoundDrawablesWithIntrinsicBounds( iconResId, 0, 0, 0 );
        }
        tabViews.add( tabView );
        tabView.setCompoundDrawablePadding (-30 );
        mTabLayout.addView( tabView, new LinearLayout.LayoutParams( 0, MATCH_PARENT, 1 ) );*/
    }
    
    /*public TabView getTabAt( int position ) {
        if ( tabViews != null && tabViews.size( ) > position )
            return tabViews.get( position );
        return null;
    }*/
    
    @Override
    public void onPageScrollStateChanged( int arg0 ) {
        if ( mListener != null ) {
            mListener.onPageScrollStateChanged( arg0 );
        }
    }
    
    @Override
    public void onPageScrolled( int arg0, float arg1, int arg2 ) {
        if ( mListener != null ) {
            mListener.onPageScrolled( arg0, arg1, arg2 );
        }
    }
    
    @Override
    public void onPageSelected( int arg0 ) {
        setCurrentItem( arg0 );
        if ( mListener != null ) {
            mListener.onPageSelected( arg0 );
        }
    }
    
    @Override
    public void setViewPager( ViewPager view ) {
        if ( mViewPager == view ) {
            return;
        }
        if ( mViewPager != null ) {
            mViewPager.setOnPageChangeListener( null );
        }
        final PagerAdapter adapter = view.getAdapter( );
        if ( adapter == null ) {
            throw new IllegalStateException( "ViewPager does not have adapter instance." );
        }
        mViewPager = view;
        view.setOnPageChangeListener( this );
        notifyDataSetChanged( );
    }
    
    public void notifyDataSetChanged( ) {
        mTabLayout.removeAllViews( );
        PagerAdapter adapter = mViewPager.getAdapter( );
        IconPagerAdapter iconAdapter = null;
        if ( adapter instanceof IconPagerAdapter ) {
            iconAdapter = (IconPagerAdapter) adapter;
        }
        final int count = adapter.getCount( );
        for ( int i = 0; i < count; i++ ) {
            CharSequence title = adapter.getPageTitle( i );
            if ( title == null ) {
                title = EMPTY_TITLE;
            }
            int iconResId = 0;
            if ( iconAdapter != null ) {
                iconResId = iconAdapter.getIconResIdNormal( i );
            }
            addTab( i, title, iconResId );
        }
        if ( mSelectedTabIndex > count ) {
            mSelectedTabIndex = count - 1;
        }
        setCurrentItem( mSelectedTabIndex );
        requestLayout( );
    }
    
    @Override
    public void setViewPager( ViewPager view, int initialPosition ) {
        setViewPager( view );
        setCurrentItem( initialPosition );
    }
    
    @Override
    public void setCurrentItem( int item ) {
        if ( mViewPager == null ) {
            throw new IllegalStateException( "ViewPager has not been bound." );
        }
        mSelectedTabIndex = item;
        mViewPager.setCurrentItem( item );
        
        final int tabCount = mTabLayout.getChildCount( );
        for ( int i = 0; i < tabCount; i++ ) {
            final LinearLayout child = (LinearLayout) mTabLayout.getChildAt( i );
            //final TabView child = (TabView) mTabLayout.getChildAt( i );
            final boolean isSelected = ( i == item );
            child.setSelected( isSelected );
            PagerAdapter adapter = mViewPager.getAdapter( );
            IconPagerAdapter iconAdapter = null;
            if ( adapter instanceof IconPagerAdapter ) {
                iconAdapter = (IconPagerAdapter) adapter;
            }
            int iconResId;
            int bgResId;
            if ( iconAdapter != null ) {
                if ( isSelected ) {
                    iconResId = iconAdapter.getIconResIdSelected( i );
                    bgResId = iconAdapter.getBackgroundResIdSelected( i );
                } else {
                    iconResId = iconAdapter.getIconResIdNormal( i );
                    bgResId = iconAdapter.getBackgroundResIdNormal( i );
                }
                
                if ( bgResId != 0 )
                    child.setBackgroundResource( bgResId );
                
                ImageView iv = (ImageView)child.findViewById( R.id.ivIcon );
                if ( iconResId != 0 ) {
                    iv.setVisibility( View.VISIBLE );
                    iv.setImageResource( iconResId );
                }else{
                    iv.setVisibility( View.GONE );
                }
            }
            
            if ( isSelected ) {
                animateToTab( item );
            }
        }
    }
    
    @Override
    public void setOnPageChangeListener( OnPageChangeListener listener ) {
        mListener = listener;
    }    
}


Finally update library's  IconPagerAdapter .java

public interface IconPagerAdapter {
    /**
     * Get icon representing the page at {@code index} in the adapter.
     */
    int getIconResIdNormal(int index);
    /**
     * Get icon representing the page at {@code index} in the adapter This will be used when the tab is selected.
     */
    int getIconResIdSelected(int index);
    // From PagerAdapter
    int getCount();
    /**
     * Get background representing the page at {@code index} in the adapter This will be used when the tab is selected.
     */
    int getBackgroundResIdSelected(int index);
    /**
     * Get background representing the page at {@code index} in the adapter..
     */
    int getBackgroundResIdNormal(int index);
}

Sunday, May 4, 2014

Downloading image blob from Google's App Engine

Last time we have seen how to upload an image to app engine. Now its time to fetch it back. There are 2 ways to do that.

1. Using serve()
2. Using urls generate for each blob.

I tried using 1st method, but it did not work. So tried out second method which did work. Following are the details of that implementation.

First get the url for download for each blob. To do that you need blob key of your blobs. In my case I have saved blob keys in a separate database entity. Blob key is returned when you create new blob.
        
String[] array = getBlobKeyByProblemId(problemId);
if (array != null && array.length > 0) {
    String[] urls = new String[array.length];
    for (int i = 0; array != null && i < array.length; i++) {
        ImagesService imagesService = ImagesServiceFactory.getImagesService();
        BlobKey key = new BlobKey(array[i]);
        ServingUrlOptions options = ServingUrlOptions.Builder.withBlobKey(key).secureUrl(true);
        urls[i] = imagesService.getServingUrl(options);
    }
}

Send this key to client, so that whenever it needs to download image it will use this url.
 
Now time to get the image. Call the following method as:
returnValue = httpPostBinary( entry.url, null );

private byte[] httpPostBinary( String url, String data ) {
        if ( url == null )
            return null;
        
        int bufferSize = 1024;
        int loopCounter = 3;
        int timeout = 1 * 60 * 1000;
        byte[] retVal = null;
        HttpURLConnection urlConnection = null;
        while ( loopCounter > 0 ) {
            try {
                retVal = null;
                Log.d( Utils.AppName, "CommunicationManager.httpPostBinary(): Communication attempt#" + loopCounter );
                byte[] buffer = new byte[bufferSize];
                URL httpUrl = new URL( url );
                // handleTrustedConnection();
                urlConnection = (HttpURLConnection) httpUrl.openConnection( );
                urlConnection.setRequestMethod( "POST" );
                urlConnection.setRequestProperty( "User-Agent", "eRecommendation" );
                urlConnection.setRequestProperty( "Accept", "*/*" );
                urlConnection.setRequestProperty( "Content-Type", "application/xml" );
                if ( data != null )
                    urlConnection.setDoOutput( true );
                else
                    urlConnection.setDoOutput( false );
                urlConnection.setConnectTimeout( timeout );
                if ( data != null ) {
                    OutputStream outputStream = urlConnection.getOutputStream( );
                    if ( outputStream != null ) {
                        DataOutputStream dataOutputStream = new DataOutputStream( outputStream );
                        dataOutputStream.writeUTF( data );
                        dataOutputStream.close( );
                        outputStream.close( );
                    }
                }
                InputStream inputStream = urlConnection.getInputStream( );
                if ( inputStream != null ) {
                    ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream( );
                    int count;
                    while ( true ) {
                        count = inputStream.read( buffer );
                        if ( count == -1 )
                            break;
                        arrayOutputStream.write( buffer, 0, count );
                    }
                    retVal = arrayOutputStream.toByteArray( );
                    arrayOutputStream.close( );
                    inputStream.close( );
                }
                if ( retVal != null && retVal.length > 0 )
                    break;
            } catch ( Exception e ) {
                Log.d( Utils.AppName, "CommunicationManager.httpPost():" + e.toString( ) );
                retVal = null;
            } finally {
                if ( urlConnection != null )
                    urlConnection.disconnect( );
            }
            try {
                Thread.sleep( 1000 );
            } catch ( InterruptedException e ) {
            }
            loopCounter--;
        }
        return retVal;
    }

retVal contains array bytes which forms the image. You can save this image in file system and use it.

Wednesday, April 2, 2014

Upload an image from Android to Google's app engine server

Recently I have started working on a project on Android. This app involves uploading few images chosen by user to the server. App engine has a document page at https://developers.google.com/appengine/docs/java/blobstore/, but unfortunately for Android developer it is not enough. This document mentions how to upload using web client i.e. using using form in a html page.

For uploading using java application we need a multipart message. We need following libs:
1. httpcore-4.3.2.jar
2. httpmime-4.3.3.jar

httpcore-4.3.2.jar and httpmime-4.3.3.jar can be found at http://hc.apache.org/downloads.cgi.These jars are part of zip file containing other files. Extract the entire zip and copy these two required files in <android project>/libs.

public void uploadBlobImage( String path, String link ) {
        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost(link);

        try {
            MultipartEntity entity = new MultipartEntity();
            entity.addPart("type", new StringBody("photo"));
            File file = new File( path );
            entity.addPart("data", new FileBody(file, ContentType.create( "image/jpg"),"Swapnil.jpg"));
            
            httppost.setEntity(entity);
            HttpResponse response = httpclient.execute(httppost);
            Log.d( Utils.AppName, "Image uploaded: "+ response.toString( ));
        } catch (Exception e) {
            Log.d( Utils.AppName, "Image not uploaded: Exception:"+ e.toString( ));
        }        
    }
Note

In my first draft of this post the code for new file body was:
new FileBody(file, ContentType.create( "image/jpg")/*,"Swapnil.jpg" */)
For some strange reason this did not work and upload used to fail. Please let me know if you know the reason.

Android aar deployment in Maven - 2022

Introduction If you are working on android library project, you might be wondering how to publish it on Maven like this . Earl...