Percentage-wise weight assignment in Stack view

Android layout weight assignment in Linear Layout is a strong feature in layout designing. But the same feature is a little different in iOS. Let’s check how can we assign weight or percentage-wise size(height/width) allocation in Stack view.

iOS has varied device sizes, e.g. iPhone SE, iPad, iPhone XS Max device have different sizes.

When we give a fixed size to view it sometimes looks improper in different devices

Check below sample.

E.g.: For below screen in iPhone SE and iPad, iPad Label1 and Label2 are looking very small.

iPad Screen
iPad Screen
2
iPhone SE screen

The above problem can be solved, if we provide percentage-wise width to labels, then it would display properly in any device size

Suppose we want Label1 and Label2 to be 20% each of the Stackview and Label3 to be 60% of the Stackview.

Check below step:

  1. Select Label1 and parent Stackview and select “Equal Widths” option.
3.png
2. Then assign 0.2(20 %) multiplier to Label1 w.r.t superview
4.png
3. Then Select Label2 and Stackview and select “Equal Widths” option.
5.png
4. Then again assign 0.2(20 %) multiplier to Label2 w.r.t Stackview same as in Label1.
6
5. Then select Label3 and Stackview add “Equal Widths” constraint and assign 0.6(60%) multiplier w.r.t Superview.
7.png
Now check in different screen sizes, the UI will look proper irrespective of screen size.
8
iPad screen after applying “Equal Width” constraints

Another way to achieve perfect size distribution is through the property “Aspect ratio”

Aspect Ratio is used the control the width and height as per the ratio you specify. If we need to maintain an aspect ratio for the view sizes, then we can use this constraint.

Check below steps:

  1. Select all the child views and add “Aspect Ratio” property

9.png

2. Now add width and height based on Stackview size.

Superview(Stackview) width is 375, we will have to divide and assign a width to each label based on the requirement(Label1 – 20%, Label2 – 20%, Label3 – 60%) and we require height to be constant 20

Label1 will be 75:20 (75(20%) will be the width and 20 will be height)

Label2 will be 75:20 (75(20%) will be the width and 20 will be height)

Label3 can be 225:20 (225(60%) will be the width and 20 will be height)

Note: Making width 75:20(20 %) means we want our width to be 20% of the Superview width.

10.png

11.png

Now if you open the simulator in both iPad, iPhone SE, etc, you’ll always have the Label1 and Label2 taking up 20% and Label 3 taking up 60% of the parent view.

The width will adjust based on the screen size. Same can be done for height also.

12
iPad screen
13
iPhone SE screen

 

HaPpY CoDiNg….

About Codable in Swift… And the common mistakes to avoid….

Often while fetching data, its format needs to be changed like in networking calls we receive data in different format like JSON, which has to be changed to different format to use it.

So most of the time we receive data in JSON format so to convert it in usable format there are many ways, like many cool libraries are available to do the work. But Swift 4 has introduced Codable protocol for easy JSON parsing. Codable is a type alias of Encodable and Decodable protocol.

The advantage which I found of using Codable than other libraries:

  • No extra decode and encode code.
  • Native support
  • Easy to understand and maintain

Lets directly dive into example and convert JSON into a model object:

JSON Sample 1:

{
 “name” : “John”,
 “lastname” : “Shark”,
 “hobby” : “Reading”,
 “DOB” : “05/03/1990”,
 “job” : “Developer”
 }

Lets first create a model class for this:

struct MyModel : Codable {
 var name : String
 var lastname : String
 var hobby : String
 var DOB : String
 var job : String
}

Now, let’s parse it:

do {
 let json = try JSONSerialization.data(withJSONObject: jsonData,   options: .prettyPrinted)
 let reqJSONStr = String(data: json as! Data, encoding: .utf8)
 let data = reqJSONStr?.data(using: .utf8)
 let decoder = JSONDecoder()
 do {
    let user = try decoder.decode(MyModel.self, from: data)
 } catch {
    print(error.localizedDescription)
 }
 } catch {
 print(“Error while deserialization of jsonData”)
 }

decoder.decode() converts JSON to model object. Added catch blocks for exception handling.

JSON Sample 2:

Added an object “Friend” in the JSON

{
 “name” : “John”,
 “lastname” : “Shark”,
 “hobby” : “Reading”,
 “DOB” : “05/03/1990”,
 “job” : “Developer”,
 “Friend” : {
 “name” : “Michael”,
 “place” : “London”,
 “job” : “Tester”
 }
}

Let’s add a model “Friend” and also modify the “MyModel” class as below

struct MyModel : Codable {
 var name : String
 var lastname : String
 var hobby : String
 var DOB : String
 var job : String
 var Friend : Friend
}
struct Friend : Codable {
 var name : String
 var place : String
 var job : String
}

Parsing code will remain the same

Now the mistake which I always make is the variable name should be exactly the same as the JSON object i.e if I change the Model class as below

struct MyModel : Codable {

 var name : String
 var lastname : String
 var hobby : String
 var DOB : String
 var job : String
 var friend : Friend 
}

Then the above code won’t work, as it is case sensitive and ‘friend’ is different from keyword ‘Friend’ in JSON, if you want to keep the variable name different then you can use the CodingKeys as below

struct MyModel : Codable {
 var name : String
 var lastname : String
 var hobby : String
 var DOB : String
 var job : String
 var friend : [Friend]

enum CodingKeys : String, CodingKey {
 case name = “name”
 case lastname = “lastname”
 case hobby = “hobby”
 case DOB = “DOB”
 case job = “job”
 case friend = “Friend”
 }
}

Now the above JSON will be parsed without error.

JSON Sample 3:

{
 “name” : “John”,
 “lastname” : 12345,
 “hobby” : “Reading”,
 “DOB” : “05/03/1990”,
 “job” : “Developer”,
 “Friend” : [
   {
    “name” : “Michael”,
    “place” : “London”,
    “job” : “Tester”
   }
  ]
}

For above JSON the code will give exception as “The data couldn’t be read because it isn’t in the correct format”.
This is the error given when the binding is not proper and it is not able to parse.
To check in which line the parsing is breaking we can make use the decode method, let’s overwrite the decode method as below :

struct MyModel : Codable {

var name : String?
var lastname : String?
var hobby : String?
var DOB : String?
var job : String?
var friend : [Friend] = []

   enum CodingKeys : String, CodingKey {
      case name = “name”
      case lastname = “lastname”
      case hobby = “hobby”
      case DOB = “DOB”
      case job = “job”
      case friend = “Friend”
}

init(from decoder: Decoder) throws {

let container = try decoder.container(keyedBy: CodingKeys.self)
if let name = try container.decode(String?.self, forKey: .name) {
    self.name = name
}
if let lastName = try container.decode(String?.self, forKey: .lastname) {
   self.lastname = lastName
}
if let hobby = try container.decode(String?.self, forKey: .hobby){
   self.hobby = hobby
}
if let DOB = try container.decode(String?.self, forKey: .DOB){
   self.DOB = DOB
}
if let job = try container.decode(String?.self, forKey: .job){
   self.job = job
}
if let Friend = try container.decode([Friend]?.self, forKey: .friend) {
   self.friend = Friend
}
}
}

Parsing ‘lastName’ will break as the value in JSON is as Int value and we have declared the datatype as String.

DataType plays a major role in Codable protocol. If the datatype of an element declared differ from the json, then it will throw an exception.

So we will just change the datatype of ‘lastname’ as ‘int’, then it will work without any error.

HaPpY CoDiNg…..

Python and Tensorflow installation in Windows for Machine learning.

TensorFLow is open source software library for Machine Intelligence by Google. It make it easier for developers to design, build, and train deep learning models…

Tensorflow is a Python library and it require Python 3.5 and higher otherwise it won’t work.

Follow the below steps to install Python and Tensorflow in Windows system.

Step 1: Install python.

To install Python go to https://www.python.org/

python11

Check out for 64 bit executable installer and download the exe file and run the exe file…

Once installation is done add it in your environment variable..

To find the environment variable in your system follow below steps:

  1. Right-click ‘Computer’ in the Navigation Tree Panel on the left.
  2. Select ‘Properties’ at the bottom of the Context Menu.
  3. Select ‘Advanced system settings’
  4. Click ‘Environment Variables…’ in the Advanced Tab

Then add python_home and python path.

  • Add in system variable name PYTHON_HOME

sub1And add python path in my case python is located in below location C:\Users\xyz..\AppData\Local\Programs\Python\Python36

  • Then add below in path variable

Append ‘;%PYTHON_HOME%\;%PYTHON_HOME%Scripts\’ at the end of the path variable.

sub2 

Restart system once done..

Too check whether Python installed. Just go to cmd prompt and type Python.

If you receive below message then Python is installed successfully.

cmd1

 

Step 2:

To install Tensorflow type below command

python -m pip install tensorflow  Untitled

To check whether tensorflow installed successfully open cmd and check with below commands.

  • python
  • import tensorflow as tf
  • tf.__version__

Untitle1

HaPpY CoDiNg…..

How to add Ripple Effect to an Android App.

Everyone likes to use the app if it behave like real time object, i.e if user touches any UI element it will have a touched effect behaves like real objects,  this is what ripple effect is all about.

Ripple effect was introduced in Lollipop Api 21(Android 5.0), so it work in all devices with Api level 21 and higher.

Ripple effect can be added to any of the view (CardView, Button, Textview, Linearlayout etc)

Two simple ways to add Ripple effect is mentioned below:

1. The simplest way is to add below code in xml file

   android:foreground="?android:attr/selectableItemBackground"
    android:clickable="true"

You can add ‘selectableItemBackground’ to the foreground or background property of the view. If there is a drawable required to be added in the view, you can add the drawable in the background and above property in the foreground.

You can use selectableItemBackground or selectableItemBackgroundBorderless property

For Ripples contained within the view use android:foreground=”? selectableItemBackground ”  and for Ripples that extend beyond the view’s bounds use
android: foreground =”?selectableItemBackgroundBorderless”.

Note: If the view is the parent and  has a child equal to the width and height of parent then the child will consume the click and ripple effect won’t be visible so add ripple effect to the view in which the click listerner is attached.

ezgif.com-video-to-gif.gif

 

2. Another way is to Add ripple in drawable file

For Ripple effect add xml files in drawable-v21, as it will takes file  for api 21 n above

Add  xml file for ripple effect in drawable-v21/login_button_bg.xml folder

<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/rippleColor"> 
 <item android:id="@android:id/mask" android:drawable="@android:color/white" />
 </ripple>

Ripple color will get applied for ripple effect. The mask layer will not be visible, as at run time, a single layer may be set as the mask using   setId(…,android.R.id.mask)  or an existing mask layer may be replaced using setDrawableByLayerId(android.R.id.mask, …). If no child layers or mask is specified and the ripple is set as a View background, then ripple will be drawn atop the first available parent background within the View’s hierarchy. In this case, the drawing region may extend outside of the Drawable bounds. So it better to add a mask or a child layer inside the ripple

If  needed to add a shape also to the drawable file then add it inside the <item> tag

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
   android:color="#7BB1FF">
     <item>
       <shape
        android:shape="rectangle">
       <solid android:color="#154bad" />
       <corners
        android:bottomRightRadius="4dip"
        android:topRightRadius="4dp" />
       </shape> 
</item>
</ripple>

 

And also add file in  drawable/login_button_bg.xml folder   (for devices with api level 21 below)

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle">
 <corners
 android:bottomRightRadius="4dip"
 android:topRightRadius="4dp" />
 </shape>

 

3. Add ripple effect at run time.

Or if you don’t want to add drawable file in drawable-v21 folder, then at wrap the ripple effect at run time.

Drawable image = getDrawable(R.drawable.login_button_bg);
 RippleDrawable rippledBg = new RippleDrawable(ColorStateList.valueOf(ContextCompat.getColor(context,R.color.colorRipple)), image, null);
 btn.setBackground(rippledBg);

 

Note: If on click of button if it navigates to some other screen and the ripple effect is not visible then simply put a delay before the method call…

final Handler handler = new Handler();
 handler.postDelayed(new Runnable() {
 @Override
 public void run() {
 loginClick();
 }
 }, 300);
}ezgif.com-video-to-gif (1)

Edittext with separate box for each letter (CustomEntryEdittext)

2017-07-10 21.55.13

Here Custom class help in addition of Edittext in different box. Specify the entry count required in xml (entryCount) then at runtime that many boxes will be drawn.

This custom class helps to enter edittext in different boxes. This feature can be used in adding OTP or while entering pin. Easy to integrate just add CustomEntryEdittext class file in your package and then add CustomEntryEdittext view in your xml file.

Steps for integrating CustomEntryEdittext in your app.

1. Add CustomEntryEdittext class  in your app.

public class CustomEntryEdittext extends LinearLayout {

    public int entryCount = 0; //count of boxes to be created
    private int currentIndex = 0;
    private static int EDITTEXT_MAX_LENGTH = 1; //character size of each editext
    private static int EDITTEXT_WIDTH = 40;
    private static int EDITTEXT_TEXTSIZE = 20; //textsize
    private boolean disableTextWatcher = false, backKeySet = false;
    private TextWatcher txtWatcher;
    private onFinishListerner mListerner;


    public CustomEntryEdittext(Context context) {
        super(context, null);
    }

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

    public CustomEntryEdittext(Context context, AttributeSet attrs, int defStyle) {
        this(context, attrs, defStyle, 0);
    }

    public CustomEntryEdittext(Context context, AttributeSet attrs, int defStyle, int defStyleRes) {
        super(context, attrs);
        init(context, attrs);
    }

    public void setOnFinishListerner(onFinishListerner listerner) {
        this.mListerner = listerner;
    }

    public interface onFinishListerner {
        void onFinish(String enteredText);
    }


    private void init(Context context, AttributeSet attrs) {

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.CustomEntryEdittext, 0, 0);


        entryCount = a.getInteger(R.styleable.CustomEntryEdittext_editextCount, 0);

        a.recycle();

        setOrientation(LinearLayout.HORIZONTAL);
        setGravity(Gravity.CENTER_VERTICAL);

        for (int i = 0; i < entryCount; i++) {

            //creates edittext based on the no. of count
            addView(initialiseAndAddChildInLayout(i, context), i);
        }

    }

    //method focuses of previous editext
    private void getPreviousEditext(int index) {
        if (index > 0) {
            EditText edtxt = (EditText) getChildAt(index - 1);
            disableTextWatcher = true;

             edtxt.setText("");
            edtxt.requestFocus();
            disableTextWatcher = false;

        }
    }

    //method focuses of previous editext
    private void getPreviousEditextFocus(int index) {
        if (index > 0) {
            EditText edtxt = (EditText) getChildAt(index - 1);
            disableTextWatcher = true;
            edtxt.requestFocus();
            disableTextWatcher = false;
        }
    }


    //method to focus on next edittext
    private void getNextEditext(int index) {
        if (index < entryCount - 1) {
            EditText edtxt = (EditText) getChildAt(index + 1);
            edtxt.requestFocus();
        }
    }


    private View initialiseAndAddChildInLayout(int index, Context context) {
        final EditText editext = new EditText(context);
        editext.setMaxWidth(1);
        editext.setTag(index);
        editext.setGravity(Gravity.CENTER);
        editext.setTextSize(EDITTEXT_TEXTSIZE);
        editext.setInputType(EditorInfo.TYPE_CLASS_NUMBER);
        editext.setFilters(new InputFilter[]{new InputFilter.LengthFilter(EDITTEXT_MAX_LENGTH)});
        LayoutParams param = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
        editext.setLayoutParams(param);
        editext.addTextChangedListener(txtWatcher = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {


            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                currentIndex = Integer.parseInt(editext.getTag().toString());


                if (editext.getText().toString().length() == 1 && !disableTextWatcher) {
                    getNextEditext(currentIndex);
                } else if (editext.getText().toString().length() == 0 && !disableTextWatcher) {// && !isFirstTimeGetFocused && !backKeySet) {
                    getPreviousEditext(currentIndex);
                }

            }

            @Override
            public void afterTextChanged(Editable s) {


            }
        });
        editext.setOnKeyListener(new OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_DEL) {
                    currentIndex = Integer.parseInt(editext.getTag().toString());
                    if (editext.getText().toString().length() == 0 && !disableTextWatcher) {
                        getPreviousEditextFocus(currentIndex);
                    } else {
                        disableTextWatcher = true;
                        editext.setText("");
                        disableTextWatcher = false;
                    }
                    backKeySet = true;
                }

                return true;
            }


        });
        editext.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
                    if(currentIndex==entryCount-1 && getEnteredText().length()==entryCount)
                    {
                        mListerner.onFinish(getEnteredText());
                    }
                }
                return false;
            }
        });

        return editext;
    }


    public String getEnteredText() {
        String strEnteredValue = "";
        for (int i = 0; i < getChildCount(); i++) {
            EditText editText = (EditText) getChildAt(i);
            if (editText.getText() != null && editText.getText().toString().length() > 0)
                strEnteredValue = strEnteredValue + editText.getText().toString();

        }
        return strEnteredValue;
    }

    public void clearCustomEntryEdittext() {
        for (int i = 0; i < getChildCount(); i++) {
            EditText editText = (EditText) getChildAt(i);
            editText.setText("");
        }
        EditText editText = (EditText) getChildAt(0);
        editText.requestFocus();
    }


2. Add below in attrs.xml file i.e values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomEntryEdittext">
        <attr name="entryCount" format="integer" />
    </declare-styleable>
</resources>

3. Add custom view in your layout

<com.custom.widget.CustomEntryEdittext
 android:id="@+id/custom_unique_edittext"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_alignParentLeft="true"
 android:layout_below="@+id/txt_Subtitle"
 android:layout_centerInParent="true"
 app:editextCount="6">
</com.custom.widget.CustomEntryEdittext>

4. Use findviewbyID in fragment to get accees to the view
customUniqueEdittext.getEnteredText() can be used in fragment to get entered data

customUniqueEdittext.setOnFinishListerner(new CustomUniqueEdittext.onFinishListerner() {
 @Override
 public void onFinish(String enteredText) {
 //Api Call
 }
 });

OnFinish listerner is called once user completes entering all the letter in editttext.

HaPpY CoDiNg……