Sunday, October 28, 2018

Populate ListView from JSON using AsyncTask via singleton class.

Populate ListView from JSON using
AsyncTask via singleton class.


In this tutorial we will try to make a simple app(not a real life one) which sends GET request and fetch response then pass the response to an array adapter function which inserts it into an ArrayList.

*Note: This is for educational purpose only, for real life situation we will use ListFragment with ArrayAdpater.


First: we will create our design which includes three XML layout files as the following:-
  1. main_activity.xml -> contains FrameLayout to act as fragment holder.
  2. fragment_layout.xml ->contains the ListView.
  3. list_item.xml -> containts the elements of the ListView.

Second: we will create our model layer which divided into:-
  1. POGO class -> To hold class of the object we want to fetch from JSON.
  2. Singletone class -> To make sure we have on instance of the ArrayList that holds our data intances.

Third: we will make our fragment (for educational purpose we will make it an ordinary fragment, but for real life situations we should use ListFragment) which do the following:-
  1. Initialize our URL string.
  2. Bind it tothe fragment_layout file.
  3. Bind to the ListView.
  4. Includes BaseAdaper as inner class(again this is for educational purpose only, for real life situation we should use ArrayAdapter) and bind it to the list_item xml file.

Fourth and last: we will write our main activity which acts as a parent activity to the fragment.


Making our layouts
1- main_activity
Our first layout file is the main_activity.xml which has a single element(FrameLayout) with id fragmentHolder to be our fragment’s id,
you can use any layout you want here U used Constrained layout because it’s more flexible to the changes of the screen size, because we have only one element, and we want it to be view in full screen we have assigned each of width and height of fragmentHolder to match_parent.


main_acitivity.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/fragmentHolder"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
>
</FrameLayout>
</android.support.constraint.ConstraintLayout>

2- fragment_layout

The second layout we will make is the fragmentLayout which holds the ListView and acts as a layout for our fragment, again we have only single element here, and we want it to fill the whole screen so, we will each of width and height to match_parent.

fragment_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/fragmentLayout"
xmlns:app="http://schemas.android.com/apk/res-auto">

<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

</ListView>

</android.support.constraint.ConstraintLayout>




3-list_item
The last layout we will make is the most important one, because it will be the one which holds our widgets and view them to the user, for those who don’t know we will deal with this layout as a separate layout(or window for clarity), so basically we will put here the items we want to view for each item in the list(for each JSON object).


list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">


<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/txtName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>


<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/txtAddress"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtName"/>


<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imgPerson"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@mipmap/ic_launcher_round"
android:contentDescription="@string/image_description"/>


</android.support.constraint.ConstraintLayout>





Backend coding
Now we are ready to create our model, the model will have two classes, the first class is a class to hold data object type in our case Person, the second one will be a singletone class to hold an ArrayList of this class’s objects.

1-the model class
this is class is seprated from the logic of the application as it acts only as a data type holder.
*In real life this will be a separated package which contains many other classes depending on probelm statement of the project.
Person.java
package com.example.nasser.android_network_tutorial;

import java.util.ArrayList;

public class Person {
private String mName;
private String mAddress;
private ArrayList<String> mWebsites;
private String mImage;

public Person(){

}
public String getImage(){
return mImage;
}

public void setImage(String image){
mImage = image;
}

public String getName(){
return mName;

}

public String getAddress(){
return mAddress;
}

public ArrayList<String> getWebsites(){
return mWebsites;
}

public void setName(String name){
this.mName = name;
}

public void setAddress(String address){
this.mAddress = address;
}

public void setWebsites(ArrayList<String> websites){
this.mWebsites = websites;
}

}


2-Singletone class
To make sure we have only one ArrayList to hold our data in the whole project, we will go through a singletone approach, singletone class is different from ordinary because its constructor is private and can be accessed through a special static function, stop talking and let’s see the example.

PersonArray.java
package com.example.nasser.android_network_tutorial;

import java.util.ArrayList;

public class PersonArray {
private static PersonArray personInstance = new PersonArray();
private ArrayList<Person> mPerson;


private PersonArray(){
mPerson = new ArrayList<Person>();
}

public static PersonArray getPersonArrayInstance(){
return personInstance;
}

public ArrayList<Person> getPersons(){
return mPerson;
}

}

3-Our fragment
in this part will have two blocks of code, the first one is the fragment which has most of the logic of the project, and the second one is our AsyncTask class that is called from the fragment

NetworkFragment.java
package com.example.nasser.android_network_tutorial;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;


public class NetworkFragment extends Fragment{
private String mUrlString;
PersonArray personArrayList;
ListView listView;
public NetworkFragment(){
personArrayList = PersonArray.getPersonArrayInstance();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState){
Log.i("NetworkFragment: ", "Started");
super.onCreate(savedInstanceState);
mUrlString = "https://api.myjson.com/bins/ge200";
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance){
View view = inflater.inflate(R.layout.fragment_layout, container, false);
listView = (ListView) view.findViewById(R.id.listView);
//Run AsyncTask to fetch the data from the server
DownloadJsonTask downloadJsonTask = new DownloadJsonTask(listView, getActivity());
downloadJsonTask.execute(mUrlString);
return view;
}
//Use this function to communicate between the current class and AsyncTask
@Override
public void onAttach(Context context){
super.onAttach(context);
}
@Override
public void onDetach(){
super.onDetach();
}
@Override
public void onDestroy(){
super.onDestroy();
}
//BaseAdapter class to fill the list view layout with data
class CustomBaseAdapter extends BaseAdapter {
Activity mContext;
public CustomBaseAdapter(Activity context){
if(context == null){
Log.e("CustomAdapter: ", "Context is null");
}else{
this.mContext = context;
}
}
@Override
public int getCount(){
return personArrayList.getPersons().size();
}
@Override
public Person getItem(int position){
return personArrayList.getPersons().get(position);
}
@Override
public long getItemId(int position){
return position;
}
//Fill list_item's layout widgets
@Override
public View getView(int position, View view, ViewGroup container){
if(view == null){
LayoutInflater inflater = (LayoutInflater) mContext.getLayoutInflater();
view = inflater.inflate(R.layout.list_item, null);
}
//Create an instance of Person class to bind it with the widgets
Person person = new Person();
person = getItem(position);
TextView txtName = (TextView) view.findViewById(R.id.txtName);
TextView txtAddress = (TextView) view.findViewById(R.id.txtAddress);
ImageView imgPerson = (ImageView) view.findViewById(R.id.imgPerson);
txtName.setText(person.getName());
txtAddress.setText(person.getAddress());
//Run another AsyncTask for images
DownloadImageTask downloadImageTask = new DownloadImageTask(imgPerson);
downloadImageTask.execute(person.getImage());
return view;
}

}

}

Now our AsyncTask class

DownloadjsonTask.java
package com.example.nasser.android_network_tutorial;

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ListView;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import javax.net.ssl.HttpsURLConnection;

public class DownloadJsonTask extends AsyncTask<String, Void, String> {
private Activity mContext;
private ListView mListView;
PersonArray personArray;
DownloadJsonTask(ListView listView, Activity context){
mListView = listView;
mContext = context;
personArray = PersonArray.getPersonArrayInstance();
}

@Override
protected void onPreExecute(){

}
//The overall process of fetching the data
@Override protected String doInBackground(String... urls){
InputStream stream = null;
HttpsURLConnection connection = null;
String result = null;
try{
URL url = new URL(urls[0]);
connection = (HttpsURLConnection) url.openConnection();
connection.setReadTimeout(3000);
connection.setConnectTimeout(3000);
connection.setRequestMethod("GET");
connection.setDoInput(true);
connection.connect();
int responseCode = connection.getResponseCode();
if(responseCode != HttpsURLConnection.HTTP_OK){
throw new IOException("HTTP error connection" + responseCode);
}
stream = connection.getInputStream();
if(stream != null){
//Claa readStroeam to convert the result into String
result = readStream(stream);
}

}catch(Exception e){
e.printStackTrace();
}
//The returned value are passed to onPostExecute function
return result;

}

@Override
protected void onPostExecute(String result){
jsonDeserializer(result);
NetworkFragment networkFragment = new NetworkFragment();
//Make sure we have the fragment's context
if(mContext == null){
Log.e("JSONTask: ", "Context is null");
}else{
//Bind data with the CustomBaseAdapter class in the NetworkFragment
NetworkFragment.CustomBaseAdapter adapter = networkFragment.new CustomBaseAdapter(mContext);
mListView.setAdapter(adapter);
//The rest of the function just used to make sure we have the right data, it doesn't apear to the user
for(int i = 0; i < personArray.getPersons().size();i++){
Log.i("Person name: ", personArray.getPersons().get(i).getName());
Log.i("Person address: ", personArray.getPersons().get(i).getAddress());
for(String website: personArray.getPersons().get(i).getWebsites()){
Log.i("Website: ", website);
}
Log.i("Person image: ", personArray.getPersons().get(i).getImage());
}
Log.i("onPost: ", "Done");
}

}

//Deserialize JSON objects and put it in the ArrayList
public void jsonDeserializer(String json_string) {
try {
JSONArray jsonArray = new JSONArray(json_string);
Log.i("jsonDes: ", String.valueOf(jsonArray.length()));
for (int i = 0; i < jsonArray.length(); i++) {
Person person = new Person();
JSONObject jsonObject = jsonArray.getJSONObject(i);
person.setName(jsonObject.getString("name"));
person.setAddress(jsonObject.getString("address"));
person.setImage(jsonObject.getString("image"));
JSONArray websites = jsonObject.getJSONArray("websites");
ArrayList<String> websites_strings = new ArrayList<String>();
for (int j = 0; j < websites.length(); j++) {
websites_strings.add(websites.getString(j));
}
person.setWebsites(websites_strings);
personArray.getPersons().add(person);
person = null;
}
} catch (Exception e) {
e.printStackTrace();
}
Log.i("JSON Deserializer: ", "Finish deserialization");

}
//Convert downloaded data into String
public String readStream(InputStream stream) throws IOException, UnsupportedEncodingException {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String readLine;
StringBuffer buffer = new StringBuffer();
while(((readLine = reader.readLine()) != null)){
buffer.append(readLine);
}
return buffer.toString();
}

}

The class before our MainActivity class is DownloadImageTask which fetches the images in the response

DownloadImageTask.java
package com.example.nasser.android_network_tutorial;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;

import java.io.InputStream;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;


public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;
public DownloadImageTask(ImageView imageView){
this.imageView = imageView;
}

@Override
protected Bitmap doInBackground(String ...params){
Log.i("AsyncBack: ", "Start background operations");
String imageUrl = params[0];
InputStream in = null;
try{
URL url = new URL(imageUrl);
HttpsURLConnection httpConn = (HttpsURLConnection) url.openConnection();
httpConn.setAllowUserInteraction(false);
httpConn.setInstanceFollowRedirects(true);
httpConn.setRequestMethod("GET");
httpConn.connect();
int resCode = httpConn.getResponseCode();
if(resCode == HttpsURLConnection.HTTP_OK){
in = httpConn.getInputStream();
}else{
return null;
}
Bitmap bitmap = BitmapFactory.decodeStream(in);
return bitmap;
}catch(Exception e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onPostExecute(Bitmap result){
if(result != null) {
imageView.setImageBitmap(result);
}else{
Log.e("Download image: ", "result is null");
}
}

}


Finaly our MainActivity from the which the project starts.

MainActivity.java
package com.example.nasser.android_network_tutorial;

import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.app.FragmentManager;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
boolean isNetworkOk = checkInternetConnection();
if(!isNetworkOk){
return;
}else {
FragmentManager fManager = getFragmentManager();
Fragment frag = fManager.findFragmentById(R.id.fragmentHolder);
frag = new NetworkFragment();
fManager.beginTransaction().add(R.id.fragmentHolder, frag).commit();
}

}

//Small but important function to check the connection the notify the user
private boolean checkInternetConnection(){
ConnectivityManager connManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connManager.getActiveNetworkInfo();
if(networkInfo == null){
Toast.makeText(this,"No default network is currently active", Toast.LENGTH_LONG).show();
return false;
}
if(!networkInfo.isConnected()){
Toast.makeText(this, "Network is not connected", Toast.LENGTH_LONG).show();
return false;
}
if(!networkInfo.isAvailable()){
Toast.makeText(this, "Network not available", Toast.LENGTH_LONG).show();
return false;
}
Toast.makeText(this, "Network ok", Toast.LENGTH_LONG).show();
return true;
}

}



Feel free to ask about anything
Bye Bye xD xD

Populate ListView from JSON using AsyncTask via singleton class.

Populate ListView from JSON using AsyncTask via singleton class. In this tutorial we will try to make a simple app(not a real life...