Model View Presenter on Android

What is MVP

  • View is a layer that displays data and reacts to user actions. On Android, this could be an Activity, a Fragment, an android.view.View or a Dialog.
  • Model is a data access layer such as database API or remote server API.
  • Presenter is a layer that provides View with data from Model. Presenter also handles background tasks.

On Android MVP is a way to separate background tasks from activities/views/fragments to make them independent of most lifecycle-related events. This way an application becomes simpler, overall application reliability increases up to 10 times, application code becomes shorter, code maintainability becomes better and developer’s life becomes happier.

Why MVP on Android

Reason 1: Keep It Stupid Simple

If you haven’t read this article yet, do it: The Kiss Principle

  • Most of the modern Android applications just use View-Model architecture.
  • Programmers are involved into fight with View complexities instead of solving business tasks.

Using only Model-View in your application you usually end up with “everything is connected with everything”.

If this diagram does not look complex, then think about each View can disappear and appear at random time. Do not forget about saving/restoring of Views. Attach a couple of background tasks to that temporary Views, and the cake is ready!

An alternative to the “everything is connected with everything” is a god object.

A god object is overcomplicated; its parts cannot be reused, tested or easily debugged and refactored.

With MVP

  • Complex tasks are split into simpler tasks and are easier to solve.
  • Smaller objects, less bugs, easier to debug.
  • Testable.

View layer with MVP becomes so simple, so it does not even need to have callbacks when requesting for data. View logic becomes very linear.

Reason 2: Background tasks

Whenever you write an Activity, a Fragment or a custom View, you can put all methods that are connected with background tasks to a different external or static class. This way your background tasks will not be connected with an Activity, will not leak memory and will not depend on Activity’s recreation. We call such object “Presenter”.

There are few different approaches to handle background tasks but non of them are as reliable as MVP is.

Why this works

Here is a little diagram that shows what happens with different application parts during a configuration change or during an out-of-memory event. Every Android developer should know this data, however this data is surprisingly hard to find.

                                          |    Case 1     |   Case 2     |    Case 3
                                          |A configuration| An activity  |  A process
                                          |   change      |   restart    |   restart
 ---------------------------------------- | ------------- | ------------ | ------------
 Dialog                                   |     reset     |    reset     |    reset
 Activity, View, Fragment                 | save/restore  | save/restore | save/restore
 Fragment with setRetainInstance(true)    |   no change   | save/restore | save/restore
 Static variables and threads             |   no change   |   no change  |    reset

Case 1: A configuration change normally happens when a user flips the screen, changes language settings, attaches an external monitor, etc. More on this event you can read here:configChanges.

Case 2: An Activity restart happens when a user has set “Don’t keep activities” checkbox in Developer’s settings and another activity becomes topmost.

Case 3: A process restart happens if there is not enough memory and the application is in the background.

Conclusion

Now you can see, a Fragment with setRetainInstance(true) does not help here – we need to save/restore such fragment’s state anyway. So we can simply throw away retained fragments to limit the number of problems.

                                          |A configuration|
                                          |   change,     |
                                          | An activity   |  A process
                                          |   restart     |   restart
 ---------------------------------------- | ------------- | -------------
 Activity, View, Fragment, DialogFragment | save/restore  | save/restore
 Static variables and threads             |   no change   |    reset

Now it looks much better. We only need to write two pieces of code to completely restore an application in any possible case:

  • save/restore for Activity, View, Fragment, DialogFragment;
  • restart background requests in case of a process restart.

The first part can done by usual means of Android API. The second part is a job for Presenter. Presenter just remembers which requests it should execute, and if a process restarts during execution, Presenter will execute them again.

You can see complete code here.

Also you can see how to make Model View Presenter on Android here.

Update Fragment ViewPager

How to Update Fragment ViewPager

My approach to update fragments within the viewpager is to use the setTag() method for any instantiated view in the instantiateItem() method. So when you want to change the data or invalidate the view that you need, you can call the findViewWithTag() method on the ViewPager to retrieve the previously instantiated view and modify/use it as you want without having to delete/create a new view each time you want to update some value.

Imagine for example that you have 100 pages with 100 TextViews and you only want to update one value periodically. With the approaches explained before, this means you are removing and instantiating 100 TextViews on each update. It does not make sense…

You can see complete code here

In my adapter

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.view.ViewGroup;

import com.thedeveloperworldisyours.refreshcurrentfragment.fragments.FirstFragment;
import com.thedeveloperworldisyours.refreshcurrentfragment.fragments.SecondFragment;
import com.thedeveloperworldisyours.refreshcurrentfragment.fragments.ThirdFragment;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by javierg on 09/01/2017.
 */

public class MyPagerAdapter extends FragmentPagerAdapter {
    private static int NUM_ITEMS = 3;
    private Map<Integer, String> mFragmentTags;
    private FragmentManager mFragmentManager;

    public MyPagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
        mFragmentManager = fragmentManager;
        mFragmentTags = new HashMap<Integer, String>();
    }

    // Returns total number of pages
    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    // Returns the fragment to display for that page
    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return FirstFragment.newInstance();
            case 1:
                return SecondFragment.newInstance();
            case 2:
                return ThirdFragment.newInstance();
            default:
                return null;
        }
    }

    // Returns the page title for the top indicator
    @Override
    public CharSequence getPageTitle(int position) {
        return "Page " + position;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object object = super.instantiateItem(container, position);
        if (object instanceof Fragment) {
            Fragment fragment = (Fragment) object;
            String tag = fragment.getTag();
            mFragmentTags.put(position, tag);
        }
        return object;
    }

    public Fragment getFragment(int position) {
        Fragment fragment = null;
        String tag = mFragmentTags.get(position);
        if (tag != null) {
            fragment = mFragmentManager.findFragmentByTag(tag);
        }
        return fragment;
    }
}

And now in my activity:

import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.thedeveloperworldisyours.refreshcurrentfragment.adapter.MyPagerAdapter;

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{

    MyPagerAdapter mAdapterViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewPager viewPager = (ViewPager) findViewById(R.id.activity_main_view_pager);
        mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(mAdapterViewPager);
        viewPager.addOnPageChangeListener(this);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {

        Fragment fragment = mAdapterViewPager.getFragment(position);
        if (fragment != null) {
            fragment.onResume();
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
}

Finally in your fragment, something like that:

import android.animation.Animator;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.RelativeLayout;

import com.thedeveloperworldisyours.refreshcurrentfragment.R;

public class FirstFragment extends Fragment {
    RelativeLayout mView;
    Context mContext;

    public FirstFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment FirstFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static FirstFragment newInstance() {
        return new FirstFragment();
    }

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_first, container, false);
        mView = (RelativeLayout) view.findViewById(R.id.fragment_first_view);
        return view;
    }

    @Override
    public void onResume() {
        super.onResume();
        onAnimationStart();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mContext = context;
    }

    public void onAnimationStart() {
        Log.d("FirstFragment","onAnimationStart()");

        // get the center for the clipping circle
        int cx = (mView.getRight());
        int cy = (mView.getBottom());

        // get the final radius for the clipping circle
        int finalRadius = Math.max(mView.getWidth(), mView.getHeight());

        // create the animator for this view (the start radius is zero)
        Animator anim =
                ViewAnimationUtils.createCircularReveal(mView, cx, cy, 0, finalRadius);
        anim.setDuration(1000);

        // make the view visible and start the animation
        mView.setVisibility(View.VISIBLE);
        anim.start();
    }

}

You can see complete code here

Asymmetric Encryption (RSA)

RSA is one of the first practical public-key cryptosystems and is widely used for secure data transmission. In such acryptosystem, the encryption key is public and differs from the decryption key which is kept secret. In RSA, this asymmetry is based on the practical difficulty of factoring the product of two large prime numbers, the factoring problem. RSA is made of the initial letters of the surnames of Ron Rivest, Adi Shamir, and Leonard Adleman, who first publicly described the algorithm in 1977. Clifford Cocks, an English mathematician, had developed an equivalent system in 1973, but it was notdeclassified until 1997.

A user of RSA creates and then publishes a public key based on two large prime numbers, along with an auxiliary value. The prime numbers must be kept secret. Anyone can use the public key to encrypt a message, but with currently published methods, if the public key is large enough, only someone with knowledge of the prime numbers can feasibly decode the message. Breaking RSA encryption is known as the RSA problem; whether it is as hard as the factoring problem remains an open question.

public class AsymmetricAlgorithmRSA {

    public static final String sTAG = "AsymmetricAlgorithmRSA";

    /**
     * Generated key pair for 1024-bit RSA encryption and decryption
     * @return
     */
    public static KeyPair generateKey() {
        KeyPair keyPair = null;
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(1024);
            keyPair = kpg.genKeyPair();

        } catch (Exception e) {
            Log.e(sTAG, String.valueOf(R.string.asymmetric_algorithm_rsa_key));
        }
        return keyPair;
    }

    /**
     * Encoded the original data with RSA private key
     * @param privateKey
     * @param encryptionText
     * @return
     */
    public static byte[] encryption(Key privateKey, String encryptionText) {
        byte[] encodedBytes = null;
        try {
            Cipher c = Cipher.getInstance("RSA");
            c.init(Cipher.ENCRYPT_MODE, privateKey);
            encodedBytes = c.doFinal(encryptionText.getBytes());
        } catch (Exception e) {
            Log.e(sTAG, String.valueOf(R.string.asymmetric_algorithm_rsa_enryption_error));
        }
        return encodedBytes;
    }

    /**
     * Decoded the encoded data with RSA public key
     * @param publicKey
     * @param encodedBytes
     * @return
     */
    public static byte[] decryption(Key publicKey, byte[] encodedBytes) {
        byte[] decodedBytes = null;
        try {
            Cipher c = Cipher.getInstance("RSA");
            c.init(Cipher.DECRYPT_MODE, publicKey);
            decodedBytes = c.doFinal(encodedBytes);
        } catch (Exception e) {
            Log.e(sTAG, String.valueOf(R.string.asymmetric_algorithm_rsa_deryption_error));
        }
        return decodedBytes;
    }
}

Example in github

And now in our activity:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.thedeveloperworldisyous.encryptionfile.utils.AsymmetricAlgorithmRSA;
import com.thedeveloperworldisyous.encryptionfile.utils.Utils;

import java.security.Key;
import java.security.KeyPair;

public class RSAActivity extends AppCompatActivity {

    public TextView mInPutTextView, mEncodedTextView, mDecoded;
    public EditText mEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rsa);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mEditText = (EditText) findViewById(R.id.activity_rsa_edit_text);
        mInPutTextView = (TextView)findViewById(R.id.activity_rsa_text_view);
        mEncodedTextView  = (TextView)findViewById(R.id.activity_rsa_encode_text_view);
        mDecoded = (TextView)findViewById(R.id.activity_rsa_decode_text_view);

        setSupportActionBar(toolbar);

    }

    public void encodeAndDecodeRSA(View view) {

        Utils.hideKeyboard(this);
        String stringText = mEditText.getText().toString();
        mEditText.setText("");
        mInPutTextView.setText(stringText);

        KeyPair keyPair = AsymmetricAlgorithmRSA.generateKey();
        Key publicKey = keyPair.getPublic();
        Key privateKey = keyPair.getPrivate();

        byte[] encodedBytes = AsymmetricAlgorithmRSA.encryption(privateKey, stringText);
        mEncodedTextView.setText(Base64.encodeToString(encodedBytes, Base64.DEFAULT));

        byte[] decodedBytes = AsymmetricAlgorithmRSA.decryption(publicKey, encodedBytes);
        mDecoded.setText(new String(decodedBytes));
    }
}

Example in github

Advanced Encryption Standard (AES)

The Advanced Encryption Standard (AES), also known as Rijndael (its original name), is a specification for the encryption of electronic data established by the U.S. National Institute of Standards and Technology (NIST) in 2001.

Encryption

AES is based on the Rijndael cipher developed by two Belgian cryptographers, Joan Daemen and Vincent Rijmen, who submitted a proposal to NIST during the AES selection process.Rijndael is a family of ciphers with different key and block sizes.

public class SymmetricAlgorithmAES {

    public static final String sTAG = "SymmetricAlgorithmAES";

    public static SecretKeySpec setUpSecrectKey() {
        // Set up secret key spec for 128-bit AES encryption and decryption
        SecretKeySpec secretKeySpec = null;
        try {
            SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
            sr.setSeed("any data used as random seed".getBytes());
            KeyGenerator kg = KeyGenerator.getInstance("AES");
            kg.init(128, sr);
            secretKeySpec = new SecretKeySpec((kg.generateKey()).getEncoded(), "AES");
        } catch (Exception e) {
            Log.e(sTAG, String.valueOf(R.string.symmetric_algorithm_aes_secret_key_error));
        }
        return secretKeySpec;
    }

    public static byte[] encryption(SecretKeySpec secretKeySpec, String  theTestText) {
        // Encode the original data with AES
        byte[] encodedBytes = null;
        try {
            Cipher c = Cipher.getInstance("AES");
            c.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            encodedBytes = c.doFinal(theTestText.getBytes());
        } catch (Exception e) {
            Log.e(sTAG, String.valueOf(R.string.symmetric_algorithm_aes_encryption));
        }

        return encodedBytes;
    }

    public static byte[] decryption(SecretKeySpec secretKeySpec, byte[]  encodedBytes) {
        // Decode the encoded data with AES
        byte[] decodedBytes = null;
        try {
            Cipher c = Cipher.getInstance("AES");
            c.init(Cipher.DECRYPT_MODE, secretKeySpec);
            decodedBytes = c.doFinal(encodedBytes);
        } catch (Exception e) {
            Log.e(sTAG, String.valueOf(R.string.symmetric_algorithm_aes_decryption));
        }

        return decodedBytes;
    }
}

Example in GitHub

And now in our activity:

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Base64;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.thedeveloperworldisyous.encryptionfile.utils.SymmetricAlgorithmAES;
import com.thedeveloperworldisyous.encryptionfile.utils.Utils;

import javax.crypto.spec.SecretKeySpec;

public class AESActivity extends AppCompatActivity {

    public TextView mInPutTextView, mEncodedTextView, mDecoded;
    public EditText mEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aes);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

        mEditText = (EditText) findViewById(R.id.activity_aes_edit_text);
        mInPutTextView = (TextView)findViewById(R.id.activity_aes_text_view);
        mEncodedTextView  = (TextView)findViewById(R.id.activity_aes_encode_text_view);
        mDecoded = (TextView)findViewById(R.id.activity_aes_decode_text_view);

        setSupportActionBar(toolbar);
    }

    public void encodeAndDecodeAES(View view) {
        Utils.hideKeyboard(this);
        String stringText = mEditText.getText().toString();
        mEditText.setText("");
        mInPutTextView.setText(stringText);
        SecretKeySpec secretKeySpec = SymmetricAlgorithmAES.setUpSecrectKey();

        byte[] encodedBytes = SymmetricAlgorithmAES.encryption(secretKeySpec, stringText);
        mEncodedTextView.setText(Base64.encodeToString(encodedBytes, Base64.DEFAULT));

        byte[] decodedBytes = SymmetricAlgorithmAES.decryption(secretKeySpec, encodedBytes);
        mDecoded.setText(new String(decodedBytes));
    }
}

Example in GitHub

Write XML in Android

When we write a XML in android, you can choose difference ways, in this case we recommend XMLSerializer, which is an efficient and maintainable way to parse XML on Android.

Implements an XML serializer supporting both DOM and SAX pretty serializing.

We put this simple line:

            xmlSerializer.setOutput(writer);
            xmlSerializer.startDocument("UTF-8", true);
            xmlSerializer.startTag(null, "doc");

            xmlSerializer.startTag(ns, country);
            xmlSerializer.text(filmObject.getCountry());
            xmlSerializer.endTag(ns, country);

            xmlSerializer.endTag(ns, "doc");
            xmlSerializer.endDocument();
            xmlSerializer.flush();

We made a example with films:

<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<doc>
    <film title="Scarface">
        <runningTime>163 min.</runningTime>
        <country>USA</country>
        <director>Brian De Palma</director>
        <cast><name>Al</name>
            <surname>Pacino</surname><name> Steven</name>
            <surname>Bauer</surname><name>Michelle</name><surname>Pfeiffer</surname>
            <name>Mary Elizabeth</name>
            <surname>Mastrantonio</surname>
        </cast>
    </film>
    <film title="Hitman">
        <runningTime>96 min.</runningTime>
        <country>USA</country>
        <director>Aleksander Bach</director>
        <cast><name>Rupert</name>
            <surname>Friend</surname>
            <name>Zachary</name>
            <surname>Quinto</surname>
            <name>Hannah</name>
            <surname>Ware</surname>
            <name>Ciarán</name>
            <surname>Hinds</surname>
        </cast>
    </film>
    <film title="Out of Africa">
        <runningTime>160 min.</runningTime>
        <country>USA</country>
        <director>Sydney Pollack</director>
        <cast>
            <name>Robert</name>
            <surname>Redford</surname>
            <name>Meryl</name>
            <surname>Streep</surname>
            <name>Klaus Maria</name>
            <surname>Brandauer</surname>
            <name>Michael</name>
            <surname>Kitchen</surname>
        </cast>
    </film>
</doc>

In your activity you can put this lines with this films:

public void writeXml(List<Film> films) {


        try {
            FileOutputStream fileOutputStream = new FileOutputStream(mFileOutPut);
            XmlSerializer xmlSerializer = Xml.newSerializer();
            StringWriter writer = new StringWriter();

            xmlSerializer.setOutput(writer);
            xmlSerializer.startDocument("UTF-8", true);
            xmlSerializer.startTag(null, "doc");

            insertFilms(xmlSerializer, films);

            xmlSerializer.endTag(ns, "doc");
            xmlSerializer.endDocument();
            xmlSerializer.flush();
            String dataWrite = writer.toString();
            fileOutputStream.write(dataWrite.getBytes());
            fileOutputStream.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();

        } catch (IllegalArgumentException e) {
            e.printStackTrace();

        } catch (IllegalStateException e) {
            e.printStackTrace();

        } catch (IOException e) {
            e.printStackTrace();

        }


    }

    public void insertFilms(XmlSerializer xmlSerializer, List<Film> films) throws IOException {
        final String film = "film";
        String title = "title";
        String runningTime = "runningTime";
        String country = "country";
        String director = "director";
        String cast = "cast";
        Film filmObject;
        for (int i=0; i<films.size(); i++) {
            filmObject = films.get(i);
            xmlSerializer.startTag(ns, film);
            xmlSerializer.attribute(ns, title, filmObject.getTitle());

            xmlSerializer.startTag(ns, runningTime);
            xmlSerializer.text(filmObject.getRunningTime());
            xmlSerializer.endTag(ns, runningTime);

            xmlSerializer.startTag(ns, country);
            xmlSerializer.text(filmObject.getCountry());
            xmlSerializer.endTag(ns, country);

            xmlSerializer.startTag(ns, director);
            xmlSerializer.text(filmObject.getDirector());
            xmlSerializer.endTag(ns, director);

            xmlSerializer.startTag(ns, cast);
            insertCast(xmlSerializer, filmObject.getCast());
            xmlSerializer.endTag(ns, cast);

            xmlSerializer.endTag(null, film);
        }

    }

    public void insertCast (XmlSerializer xmlSerializer, List<Actor> cast) throws IOException {
        String name = "name";
        String surname = "surname";
        Actor actor;
        for (int i=0; i<cast.size(); i++) {
            actor = cast.get(i);
            xmlSerializer.startTag(null, name);
            xmlSerializer.text(actor.getName());
            xmlSerializer.endTag(null, name);

            xmlSerializer.startTag(null, surname);
            xmlSerializer.text(actor.getSurname());
            xmlSerializer.endTag(null, surname);
        }

    }

Example in GitHub