Profile

Cover photo
Amokrane Chentir
Works at Lookout Mobile Security
Attended ISIMA
366 followers|57,931 views
AboutPostsPhotosYouTube

Stream

Amokrane Chentir

Shared publicly  - 
 
 
"Programmers are easily scared and will start to cry. Violently."
Dafuq?
5 comments on original post
1
Add a comment...

Amokrane Chentir

Shared publicly  - 
 
 
Android App Polishing: Focusing on Content Rather than Brand

With the release of Capitaine Train for Android[0], several people recently asked me how we implemented some of the tips & tricks available in the application (some of these tricks are barely visible but remember, I love details :p). In order to showcase some of the most interesting application bits of code, I thought it could be helpful to Android developers to start a small series of posts. Feel free to comment this article if you want me to describe something you found nice in the Capitaine Train application.

When I starting thinking about the Capitaine Train Android app design, I clearly wanted to create a design that focuses on what users want. Indeed, as a transportation/travel application, the Capitaine Train Android app can be considered as a utility app used everyday. As a consequence, it was clear to me, the application overall design had to be content oriented. The best example of this content-oriented strategy is probably the main screen which is made of three different tabs embedded in the ActionBar: search, cart, tickets. Embedding tabs in the ActionBar clearly gives screen real estate to the content but also reduces brand visibility.

With the initial release, a few people complained about not having the brand displayed on the main screen. I totally agree with that but I want to stay clear here: this was a completely wanted and assumed choice. The main screen adresses 80% of the Capitaine Train's use cases. Not showing the brand here and focusing on users' needs is totally appropriate to me. On a side note, the brand is visible almost everywhere else: notifications, up button, authentication screen, etc.

Implementing embedded tabs was not as simple as many people would think. Indeed, by default, the Android framework automatically embed tabs inside the ActionBar except when the application is running on a phone and is in portrait. Looking at the framework code, there is a private "setHasEmbeddedTabs(boolean)"[1] method that could have been useful in this case but I didn't want to go on the dark side of the force (yep reflection on private framework features is bad). Moreover, I wanted to tweak the default tabs implementation in order to have a sliding selector that follows the ViewPager current swiping state.

In order to implement tabs I decided to go for an entirely custom solution. I created the tab bar from scratch and added it as a custom ActionBar View. The tabs container is called TabBarView and extends LinearLayout. TabBarView actually handles the "selection strip":

public class TabBarView extends LinearLayout {

    private static final int STRIP_HEIGHT = 6;

    public final Paint mPaint;

    private int mStripHeight;
    private float mOffset;
    private int mSelectedTab = -1;

    public TabBarView(Context context) {
        this(context, null);
    }

    public TabBarView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.actionBarTabBarStyle);
    }

    public TabBarView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);

        mStripHeight = (int) (STRIP_HEIGHT * getResources().getDisplayMetrics().density + .5f);
    }

    public void setStripColor(int color) {
        if (mPaint.getColor() != color) {
            mPaint.setColor(color);
            invalidate();
        }
    }

    public void setStripHeight(int height) {
        if (mStripHeight != height) {
            mStripHeight = height;
            invalidate();
        }
    }

    public void setSelectedTab(int tabIndex) {
        if (tabIndex < 0) {
            tabIndex = 0;
        }
        final int childCount = getChildCount();
        if (tabIndex >= childCount) {
            tabIndex = childCount - 1;
        }
        if (mSelectedTab != tabIndex) {
            mSelectedTab = tabIndex;
            invalidate();
        }
    }

    public void setOffset(float offset) {
        if (mOffset != offset) {
            mOffset = offset;
            invalidate();
        }
    }

   @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        // Draw the strip manually
        final View child = getChildAt(mSelectedTab);
        if (child != null) {
            int left = child.getLeft();
            int right = child.getRight();
            if (mOffset > 0) {
                final View nextChild = getChildAt(mSelectedTab + 1);
                if (nextChild != null) {
                    left = (int) (child.getLeft() + mOffset * (nextChild.getLeft() - child.getLeft()));
                    right = (int) (child.getRight() + mOffset * (nextChild.getRight() - child.getRight()));
                }
            }
            canvas.drawRect(left, getHeight() - mStripHeight, right, getHeight(), mPaint);
        }
    }
}

The TabBarView is then filled with several TabView (one for each tab). The main purpose of TabView is to provide a nice API to deal with icon and the optional text (only visible in landscape) and handle the long press gesture (long pressing a tab displays a hint):

public class TabView extends LinearLayout {

    private ImageView mImageView;
    private TextView mTextView;

    public TabView(Context context) {
        this(context, null);
    }

    public TabView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.actionBarTabStyle);
    }

    public TabView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        LayoutInflater.from(context).inflate(R.layout.tab_view_merge, this);

        mImageView = (ImageView) findViewById(R.id.image);
        mTextView = (TextView) findViewById(R.id.text);
    }

    public void setIcon(int resId) {
        setIcon(getContext().getResources().getDrawable(resId));
    }

    public void setIcon(Drawable icon) {
        if (icon != null) {
            mImageView.setVisibility(View.VISIBLE);
            mImageView.setImageDrawable(icon);
        } else {
            mImageView.setImageResource(View.GONE);
        }
    }

    public void setText(int resId) {
        setText(getContext().getString(resId));
    }

    public void setText(CharSequence text) {
        if (!TextUtils.isEmpty(text)) {
            if (mTextView != null) {
                mTextView.setVisibility(View.VISIBLE);
                mTextView.setText(text);
            }
        } else {
            if (mTextView != null) {
                mTextView.setVisibility(View.GONE);
            }
        }
        updateHint();
    }

   @Override
    public void setContentDescription(CharSequence contentDescription) {
        super.setContentDescription(contentDescription);
        updateHint();
    }

    private void updateHint() {
        boolean needHint = false;
        if (mTextView == null || mTextView.getVisibility() == View.GONE) {
            if (!TextUtils.isEmpty(getContentDescription())) {
                needHint = true;
            } else {
                needHint = false;
            }
        }

        if (needHint) {
            setOnLongClickListener(mOnLongClickListener);
        } else {
            setOnLongClickListener(null);
            setLongClickable(false);
        }
    }

    private OnLongClickListener mOnLongClickListener = new OnLongClickListener() {
       @Override
        public boolean onLongClick(View v) {
            final int[] screenPos = new int[2];
            getLocationOnScreen(screenPos);

            final Context context = getContext();
            final int width = getWidth();
            final int height = getHeight();
            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;

            Toast cheatSheet = Toast.makeText(context, getContentDescription(), Toast.LENGTH_SHORT);

            // Show under the tab
            cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, (screenPos[0] + width / 2) - screenWidth / 2,
                    height);

            cheatSheet.show();
            return true;
        }
    };

}

Once the TabBarView is initialized with a bunch of TabView, it is added as a custom View to the ActionBar:

getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
getActionBar().setCustomView(R.layout.tab_bar);

Finally an OnPageChangeListener is set to ViewPager to be notified of scroll events. These events are forwarded to the TabBarView so that it can redraw the selection strip accordingly.

Embedding tabs inside the ActionBar obviously reduces the amount of space usually dedicated to brand. When designing an application always keep in mind the actual purpose of an app. Does it exist to make your brand radiate/shine? Does it exist to address users needs? As you can see in the Capitaine Train Android app, the choice was obvious :).

#gde   #android   #AndroidAppPolishing  

[0]: https://play.google.com/store/apps/details?id=com.capitainetrain.android
[1]: https://github.com/android/platform_frameworks_base/blob/dbccd44a638ae8705a5b14bff8b2dd74abc26045/core/java/com/android/internal/app/ActionBarImpl.java#L217-L239
1
Add a comment...

Amokrane Chentir

Shared publicly  - 
1
Add a comment...

Amokrane Chentir

Shared publicly  - 
 
 
Very excited about the progress the team is making with self-driving cars.  We're going to solve city streets! Great detailed article here.
The vehicle has now moved beyond highways to its next phase: roaming the roads of Mountain View.
1
Add a comment...

Amokrane Chentir

Shared publicly  - 
 
 
After struggling with trying to figure out how various pieces fit together, I've done some research and put together the complete Android Activity/Fragment lifecycle chart. This has two parallel lifecycles (activities and fragments) which are organized vertically by time. Lifecycle stages will occur in the vertical order in which they're displayed, across activities and fragments. In this way, you can see how your fragments interact with your activities.

In addition to the attached image, I've also got an SVG: http://staticfree.info/~steve/complete_android_fragment_lifecycle.svg which is suitable for printing.

If this is missing lifecycle steps or is inaccurate in any way, let me know so I can update it!

#Android #androiddev  
1
Add a comment...

Amokrane Chentir

Shared publicly  - 
 
 
The website goes on to provide technical details and remediation advice, but you can already tell your boss “I can’t do that today, boss.  We have to respond to heartbleed.com.”  If he spends even 30 seconds glancing at that executive summary he’ll say “Crikey.  Yep, you do.”  I particularly liked the recognition that most remediation of Heartbleed would be done by businesses, which is probably why the writer focused on “business critical documents” rather than the more anodyne “data.”  Data gets weighed by the gigabyte but business critical documents spur immediate action when threatened.
1
Add a comment...
In his circles
359 people
Have him in circles
366 people
‫مدونة اخبار‬‎'s profile photo
Sidina Biha's profile photo
‫صهيب سعد الدين‬‎'s profile photo
Algerie Foirexpo's profile photo
Richa Misra's profile photo
Brahim Belarbi's profile photo
Hasnaoui Nassim's profile photo
amir arab's profile photo
Saddam Afia's profile photo

Amokrane Chentir

Shared publicly  - 
 
Hodor!
1
Add a comment...

Amokrane Chentir

Shared publicly  - 
 
 
Hey, Android Graphics Geeks:
Read. This.

What every developer should know about Surface, SurfaceHolder, EGLSurface, SurfaceView, GLSurfaceView, SurfaceTexture, TextureView, and SurfaceFlinger

http://source.android.com/devices/graphics/architecture.html
1
Add a comment...

Amokrane Chentir

Shared publicly  - 
 
 
Android App Polishing: Preserving Context to Help Users

With the release of Capitaine Train for Android[0], several people recently asked me how we implemented some of the tips & tricks available in the application (some of these tricks are barely visible but remember, I love details :p). In order to showcase some of the most interesting application bits of code, I thought it could be helpful to Android developers to start a small series of posts. Feel free to comment this article if you want me to describe something you found nice in the Capitaine Train application.

Just like every other applications, The Capitaine Train app contains several forms: sign in/up form, user info forms, etc. Forms are usually a pain in the ass to develop because you need to make sure they are clean enough not to annoy the user but verbose enough to let her/him understand what kind of information she/he is currently filling in.

When implementing the first internal versions of the application we obviously used EditText as our favorite class. Unfortunately, we rapidly faced an issue with the default design of EditText: the user loses context once at least one character is typed into a field. Because EditText uses the "hint" string as the field's description, we had some pre-filled fields that were almost impossible to understand.

In order to overcome this issue, we decided to implement an emerging pattern introduced by Matt D. Smith on Dribble[1] and described by Brad Frost on his blog[2]: the float label pattern. We actually called it internally (i.e the classes are named after): the floating hint pattern.

The implementation is rather simple and consists on a base class we called FloatingHintControl. FloatingHintControl is an abstract container that is made of a TextView and requires an additional 'interactable' View (i.e. the control). The floating hint is displayed/animated depending on the control state as reported by subclasses. In the current public build of Capitaine Train, we have several FloatingHintControl subclasses:

  • FloatingHintAutoCompleteTextView: an AutoCompleteTextView with a floating hint
  • FloatingHintButton: this UI widget is actually a button that displays a date picker dialog when tapped
  • FloatingHintEditText: the floating hint equivalent of an EditText
  • FloatingHintSpinner: A Spinner that always displays a floating hint description

Preserving user's context is both essential and difficult to do on mobile applications. Because screens sizes are generally small you always have to weight the pros and cons of adding information on screen. If you are not really satisfied about the built-in EditText, the floating hint may be a great compromise when designing forms in your Android applications.

#gde   #android   #AndroidAppPolishing  

[O]: https://play.google.com/store/apps/details?id=com.capitainetrain.android
[1]: https://dribbble.com/shots/1254439--GIF-Float-Label-Form-Interaction?list=users&offset=28
[2]: http://bradfrostweb.com/blog/post/float-label-pattern/
1
Add a comment...

Amokrane Chentir

Shared publicly  - 
 
Mind blown!
 
Looks like Google has hidden registration links for #googleio in random Developer pages (hint look at the bottom):

https://developers.google.com/analytics/devguides/collection/android/v4/

Found one valid one!

#googleio #io14
1
Add a comment...

Amokrane Chentir

Shared publicly  - 
1
Add a comment...
People
In his circles
359 people
Have him in circles
366 people
‫مدونة اخبار‬‎'s profile photo
Sidina Biha's profile photo
‫صهيب سعد الدين‬‎'s profile photo
Algerie Foirexpo's profile photo
Richa Misra's profile photo
Brahim Belarbi's profile photo
Hasnaoui Nassim's profile photo
amir arab's profile photo
Saddam Afia's profile photo
Work
Occupation
Web & Mobile Developer
Employment
  • Lookout Mobile Security
    Client Software Engineer, 2012 - present
  • KerniX
    Mobile Developer, 2011 - 2012
  • SubMate
    Software Developer Intern, 2011 - 2011
  • Trace One
    Software Developer Intern, 2010 - 2010
Basic Information
Gender
Male
Story
Introduction
Software Engineer at Lookout Mobile Security.
Education
  • ISIMA
    Software Engineering, 2009 - 2011