com.fox.myappstore.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.fox.myappstore.MainActivity.java

Source

package com.fox.myappstore;

import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

import com.fox.myappstore.data.FreeAppModel;
import com.fox.myappstore.data.ServerResponse;
import com.fox.myappstore.data.detailModels.DetailResponseModel;
import com.fox.myappstore.request.AppListRequest;
import com.fox.myappstore.request.AppLookupRequest;
import com.fox.myappstore.request.RecommendedAppsRequest;
import com.fox.myappstore.request.core.HttpRequestImpl;
import com.fox.myappstore.utils.GsonHelper;
import com.fox.myappstore.utils.NetworkHelper;
import com.fox.myappstore.utils.SharedPrefs;
import com.fox.myappstore.utils.concurrent.AsyncLoaderTask;
import com.fox.myappstore.utils.concurrent.MyHandler;
import com.fox.myappstore.utils.concurrent.MyHandlerCallback;
import com.fox.myappstore.widgets.MyBaseAdapter;
import com.fox.myappstore.widgets.RecommendedAdapter;
import com.fox.myappstore.widgets.callbacks.CustomListener;
import com.fox.myappstore.widgets.callbacks.OnTaskCompleted;
import com.google.gson.Gson;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Copyright 2017 RavicPN
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

public class MainActivity extends AppCompatActivity
        implements CustomListener, OnTaskCompleted, MyHandlerCallback, SwipeRefreshLayout.OnRefreshListener {
    private final String TAG = this.getClass().getSimpleName();

    // Event ids.
    private final int EVENT_GET_RECOMMENDED_APPS = 1;
    private final int EVENT_GET_APPS_LIST = 2;
    private final int EVENT_QUERY_APPS_LIST = 3;
    private final int EVENT_QUERY_APP_DETAIL = 4;
    private final int EVENT_REQUEST_PERMISSION_FOR_EXT_WRITE = 101;

    // View objects.
    public View recommendedView;
    public RecyclerView rvBase, rvRecommended;
    public MenuItem menuItem;
    public SwipeRefreshLayout swipeRefreshLayout;
    public CoordinatorLayout coordinatorLayout;
    public Snackbar snackBar;

    // Custom objects.
    private MyHandler mHandler = new MyHandler(this);
    public RecommendedAdapter recommendedAdapter;
    public MyBaseAdapter myBaseAdapter;
    private List<FreeAppModel> appList = new ArrayList<>();
    private List<FreeAppModel> cachedAppList = new ArrayList<>();
    private List<FreeAppModel> recommendedApps = new ArrayList<>();
    private List<FreeAppModel> cachedRecommendedApps = new ArrayList<>();
    private ServerResponse recommendedResponse;
    private ServerResponse appsListResponse;

    // Primitives.
    private int paginationStartPosition = 10;
    private int pageSize = 0;
    private boolean permission = false;

    // Multi threading stuff.
    private final int KEEP_ALIVE_TIME = 1;
    private final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
    private final int MIN_NUM_OF_CORES = 1;
    private final int MAX_NUM_OF_CORES = Runtime.getRuntime().availableProcessors();
    private final BlockingQueue<Runnable> RUNNABLES = new LinkedBlockingQueue<>();
    private final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(MIN_NUM_OF_CORES, MAX_NUM_OF_CORES,
            KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, RUNNABLES);

    // Network service stuff.
    private INetworkService mService;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = INetworkService.Stub.asInterface(service);
            try {
                mService.registerNetworkCallback(new INetworkServiceCallback.Stub() {
                    @Override
                    public void onConnectionStatusChanged(String status) throws RemoteException {
                        if (NetworkService.NETWORK_STATE_DISCONNECTED.equals(status)) {
                            if (snackBar != null)
                                snackBar.show();
                        } else if (NetworkService.NETWORK_STATE_RECONNECTED.equals(status)) {
                            if (swipeRefreshLayout.isRefreshing()) {
                                fetchAppListFromServer();
                                fetchRecommendedAppFromFromServer();
                            }
                            if (snackBar != null)
                                snackBar.dismiss();
                        }
                    }
                });
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindNetworkService();

        //Ask for permission
        if (ActivityCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
                    EVENT_REQUEST_PERMISSION_FOR_EXT_WRITE);
        }

        coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout);

        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        swipeRefreshLayout.setOnRefreshListener(this);

        snackBar = Snackbar.make(coordinatorLayout,
                "Network seems to be disconnected :(\n Reconnect and pull to refresh.", Snackbar.LENGTH_INDEFINITE);

        setupAppListView();
        setupRecommendedView();

        if (NetworkHelper.isConnected(this)) {
            fetchAppListFromServer();
            fetchRecommendedAppFromFromServer();
        } else {
            snackBar.show();
            fetchAppListFromCache();
            fetchRecommendedAppsFromCache();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_search, menu);
        setupSearchView(menu);
        return super.onCreateOptionsMenu(menu);
    }

    private void bindNetworkService() {
        Intent intent = new Intent(this, NetworkService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    private void setupAppListView() {
        rvBase = (RecyclerView) findViewById(R.id.rv_main);
        rvBase.setLayoutManager(new LinearLayoutManager(this));
        rvBase.setHasFixedSize(true);

        myBaseAdapter = new MyBaseAdapter(rvBase);
        myBaseAdapter.addCustomListener(this);
        rvBase.setAdapter(myBaseAdapter);
    }

    private void setupRecommendedView() {
        recommendedView = LayoutInflater.from(this).inflate(R.layout.recommended_view, rvBase, false);
        rvRecommended = (RecyclerView) recommendedView.findViewById(R.id.rv_recommended);
        rvRecommended.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        rvRecommended.setHasFixedSize(true);
        recommendedAdapter = new RecommendedAdapter();
        rvRecommended.setAdapter(recommendedAdapter);
        recommendedAdapter.addCustomListener(this);

        myBaseAdapter.setRecommendedView(recommendedView);
        myBaseAdapter.notifyDataSetChanged();
    }

    private void setupSearchView(Menu menu) {
        menuItem = menu.findItem(R.id.action_search);
        SearchView searchView = (SearchView) menuItem.getActionView();
        searchView.setIconifiedByDefault(false);
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                if (!TextUtils.isEmpty(newText)) {
                    searchForApps(newText);
                    searchForRecommendedApps(newText);
                }
                return true;
            }
        });
        searchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    menuItem.collapseActionView();
                    myBaseAdapter.restoreData();
                    recommendedAdapter.restoreData();
                }
            }
        });
    }

    private void searchForApps(String query) {
        final List<FreeAppModel> filtered;
        if (appList.size() > 0) {
            filtered = filter(appList, query);
        } else {
            filtered = filter(cachedAppList, query);
        }

        myBaseAdapter.setAppListData(filtered);
        myBaseAdapter.notifyDataSetChanged();
    }

    private List<FreeAppModel> filter(List<FreeAppModel> data, String query) {
        query = query.toLowerCase();
        final List<FreeAppModel> filtered = new ArrayList<>();
        for (FreeAppModel model : data) {
            final String name = model.getAppNameModel().getName().toLowerCase();
            final String category = model.getAppCategoryModel().getAppAttributes().getLabel().toLowerCase();
            final String author = model.getAppArtistModel().getLabel().toLowerCase();
            final String summary = model.getAppSummaryModel().getSummary().toLowerCase();
            if (name.contains(query) || category.contains(query) || author.contains(query)
                    || summary.contains(query)) {
                filtered.add(model);
            }
        }
        return filtered;
    }

    private void fetchAppListFromServer() {
        new AppListRequest(mHandler, EVENT_GET_APPS_LIST).sendRequest();
    }

    private void fetchRecommendedAppFromFromServer() {
        new RecommendedAppsRequest(mHandler, EVENT_GET_RECOMMENDED_APPS).sendRequest();

    }

    private void fetchAppListFromCache() {
        boolean firstTimeOpen = PreferenceManager.getDefaultSharedPreferences(this)
                .getBoolean(SharedPrefs.INITIAL_OPEN, true);
        Log.v(TAG, "first time open = " + firstTimeOpen);
        AsyncLoaderTask task = new AsyncLoaderTask(this, "cachedApps.json", AsyncLoaderTask.EVENT_APP_LIST,
                firstTimeOpen).addOnTaskCompletedListener(this);
        task.execute();
    }

    private void fetchRecommendedAppsFromCache() {
        boolean firstTimeOpen = PreferenceManager.getDefaultSharedPreferences(this)
                .getBoolean(SharedPrefs.INITIAL_OPEN, true);
        Log.v(TAG, "first time open = " + firstTimeOpen);
        AsyncLoaderTask task = new AsyncLoaderTask(this, "recommended.json", AsyncLoaderTask.EVENT_RECOMMEND,
                firstTimeOpen).addOnTaskCompletedListener(this);
        task.execute();
    }

    private void queryAppDetail(List<FreeAppModel> models) {
        for (FreeAppModel model : models) {
            int id = model.getAppIdModel().getAppAttributes().getId();
            new AppLookupRequest(mHandler, EVENT_QUERY_APP_DETAIL, id).sendRequest();
        }
        if (permission) {
            persistAppList();
        }
    }

    private void setupRatingModules(Bundle bundle) {
        String responseStr = bundle.getString(HttpRequestImpl.HTTP_RESPONSE_BODY);
        DetailResponseModel response = GsonHelper.fromJson(responseStr, DetailResponseModel.class);
        if (null != response) {
            float rating = response.getModels().get(0).getAverageUserRating();
            int amount = response.getModels().get(0).getUserRatingCount();
            int id = response.getModels().get(0).getAppId();
            //            Log.i( TAG, "id : " + id + "-----rating : " + rating );
            for (int i = 0; i < appList.size(); i++) {
                if (appList.get(i).getAppIdModel().getAppAttributes().getId() == id) {
                    appList.get(i).setUserRating(rating);
                    appList.get(i).setRatingAmount(amount);
                }
            }
        }
    }

    private void persistRecommendedApps() {
        EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Log.d(TAG, "persisting data" + Environment.getExternalStorageDirectory());
                    String jsonStr = new Gson().toJson(recommendedResponse);
                    FileWriter file = new FileWriter(
                            Environment.getExternalStorageDirectory() + "/" + "recommended.json");
                    file.write(jsonStr);
                    file.flush();
                    file.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        });
    }

    private void persistAppList() {
        EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Log.d(TAG, "persisting data" + Environment.getExternalStorageDirectory());
                    String jsonStr = new Gson().toJson(appsListResponse);
                    FileWriter file = new FileWriter(
                            Environment.getExternalStorageDirectory() + "/" + "cachedApps.json");
                    file.write(jsonStr);
                    file.flush();
                    file.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        });
    }

    private void setupRecommendedFeed(Bundle bundle) {
        String responseStr = bundle.getString(HttpRequestImpl.HTTP_RESPONSE_BODY);
        ServerResponse response = GsonHelper.fromJson(responseStr, ServerResponse.class);
        if (null != response) {
            recommendedApps.clear();
            ServerResponse.FeedModel feed = response.getFeed();
            for (FreeAppModel model : feed.getFreeAppModels()) {
                recommendedApps.add(model);
            }
            updateRecommendedAdapter(recommendedApps);
            recommendedResponse = response;
            if (permission) {
                persistRecommendedApps();
            }
        }
    }

    private void setupAppListFeed(Bundle bundle) {
        String responseStr = bundle.getString(HttpRequestImpl.HTTP_RESPONSE_BODY);
        ServerResponse response = GsonHelper.fromJson(responseStr, ServerResponse.class);
        if (null != response) {
            appList.clear();
            ServerResponse.FeedModel feed = response.getFeed();
            for (FreeAppModel model : feed.getFreeAppModels()) {
                appList.add(model);
            }
            queryAppDetail(appList);
        }
        appsListResponse = response;
        persistAppList();
        notifyRefreshLayout();
    }

    private void updateRecommendedAdapter(List<FreeAppModel> newModels) {
        recommendedAdapter.setRecommendedData(newModels);
        recommendedAdapter.notifyDataSetChanged();
    }

    private void updateAppListAdapter(List<FreeAppModel> newModels) {
        myBaseAdapter.setAppListData(newModels.subList(0, 9));
        myBaseAdapter.notifyDataSetChanged();
    }

    private void searchForRecommendedApps(String query) {
        recommendedAdapter.getFilter().filter(query);
    }

    private void notifyRefreshLayout() {
        if (swipeRefreshLayout.isRefreshing()) {
            swipeRefreshLayout.setRefreshing(false);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(SharedPrefs.INITIAL_OPEN, false)
                .apply();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        appList.clear();
        recommendedApps.clear();
        cachedAppList.clear();
        cachedRecommendedApps.clear();
        super.onDestroy();
    }

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onRestoreInstanceState(savedInstanceState, persistentState);
    }

    @Override
    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
        super.onSaveInstanceState(outState, outPersistentState);
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }

    @Override
    public void onRefresh() {
        if (NetworkHelper.isConnected(this)) {
            myBaseAdapter.resetLoader();
            pageSize = 0;
            paginationStartPosition = 10;
            // query new data set from server.
            fetchAppListFromServer();
            fetchRecommendedAppFromFromServer();
        } else {
            snackBar.show();
        }
    }

    /**
     * Custom Callbacks
     */
    @Override
    public void onTaskCompleted(int eventId, Object output) {
        //        Log.d( TAG, "onTaskCompleted" );
        switch (eventId) {
        case AsyncLoaderTask.EVENT_APP_LIST: {
            if (output.getClass().isAssignableFrom(ServerResponse.class)) {
                cachedAppList = ((ServerResponse) output).getFeed().getFreeAppModels();
                myBaseAdapter.setAppListData(cachedAppList.subList(0, 9)); // hardcoded range.
                myBaseAdapter.notifyDataSetChanged();
            }
            break;
        }
        case AsyncLoaderTask.EVENT_RECOMMEND: {
            if (output.getClass().isAssignableFrom(ServerResponse.class)) {
                cachedRecommendedApps = ((ServerResponse) output).getFeed().getFreeAppModels();
                recommendedAdapter.setRecommendedData(cachedRecommendedApps);
                recommendedAdapter.notifyDataSetChanged();
            }
            break;
        }
        default:
            break;
        }
    }

    @Override
    public void onItemClick(Object model) {
    }

    @Override
    public void onLoadMore() {
        //        Log.d( TAG, "onLoadMore(" + pageSize + ")" );
        if (pageSize == 9) {
            //            Log.d( TAG, "page size is 9" );
            return;
        } else {
            if (NetworkHelper.isConnected(this)) {
                myBaseAdapter.setLoading(true);
                mHandler.sendEmptyMessage(EVENT_QUERY_APPS_LIST);
                ++pageSize;
            }
        }
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case EVENT_GET_RECOMMENDED_APPS: {
            Bundle bundle = msg.getData();
            if (bundle.getBoolean(HttpRequestImpl.IS_SUCCESSFUL)) {
                setupRecommendedFeed(bundle);
            }
            break;
        }
        case EVENT_GET_APPS_LIST: {
            Bundle bundle = msg.getData();
            if (bundle.getBoolean(HttpRequestImpl.IS_SUCCESSFUL)) {
                setupAppListFeed(bundle);
            }
            break;
        }
        case EVENT_QUERY_APPS_LIST: {
            int paginationEndPosition = paginationStartPosition + 10;
            List<FreeAppModel> models;
            if (appList.size() > 0) {
                Log.i(TAG, "using appList");
                models = appList.subList(paginationStartPosition, paginationEndPosition);
            } else {
                Log.i(TAG, "using cache");
                models = cachedAppList.subList(paginationStartPosition, paginationEndPosition);
            }
            myBaseAdapter.updateAppListData(models);
            myBaseAdapter.setLoading(false);
            myBaseAdapter.notifyDataSetChanged();
            paginationStartPosition = paginationEndPosition;
            myBaseAdapter.onLoadFinished();
            break;
        }

        case EVENT_QUERY_APP_DETAIL: {
            Bundle bundle = msg.getData();
            if (bundle.getBoolean(HttpRequestImpl.IS_SUCCESSFUL)) {
                setupRatingModules(bundle);
                updateAppListAdapter(appList);
                break;
            }
            break;
        }
        default:
            break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        switch (requestCode) {
        case EVENT_REQUEST_PERMISSION_FOR_EXT_WRITE: {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                permission = true;
            } else {
                permission = false;
                Toast.makeText(this, "Please enable write permission for offline cache", Toast.LENGTH_SHORT).show();
            }
            break;
        }
        default: {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
        }
    }
}