Skip to content

Instantly share code, notes, and snippets.

@boshajones
Created May 30, 2018 21:30
Show Gist options
  • Save boshajones/75c926fd7f8e3bbde077f2b6767aa6cd to your computer and use it in GitHub Desktop.
Save boshajones/75c926fd7f8e3bbde077f2b6767aa6cd to your computer and use it in GitHub Desktop.
package com.hedgehoglab.hedgehoglab;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.support.annotation.AnimRes;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import com.hedgehoglab.hedgehoglab.R;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import net.hockeyapp.android.CrashManager;
import net.hockeyapp.android.UpdateManager;
/**
* Base Activity containing methods common to all single Fragment {@link Activity} objects in the application,
* including swapping the current fragment, and handling any {@link Fragment} objects that wish to intercept
* the Android back button using the {@link OnBackPressedListener}
*
* @author hedgehog lab
* @version 1.0
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public abstract class ActivityBase extends AppCompatActivity {
@AnimRes private static final int ENTER_ANIMATION = R.anim.slide_in_from_bottom;
@AnimRes private static final int EXIT_ANIMATION = R.anim.fade_out;
@AnimRes private static final int POP_ENTER_ANIMATION = R.anim.fade_in;
@AnimRes private static final int POP_EXIT_ANIMATION = R.anim.slide_out_to_bottom;
/**
* Classes that implement the OnBackPressedListener wish to intercept the Android system back
* button from the activity. Implementing classes should return true if the back event was consumed,
* else false.
*/
public interface OnBackPressedListener {
boolean onBackPressed();
}
/**
* Returns the resource ID value of the View container whose fragment(s) are to be replaced
*
* @return Resource ID
*/
protected abstract @IdRes int getContainerViewId();
/**
* When the activity is resumed, if crash reports are enabled in the current product flavour the
* HockeyApp crash manager is triggered and checks if a new crash was created before.
* If there a crash log is present, it presents a dialog to ask the user whether they want to
* send the crash log to HockeyApp.
*
* In addition, if update checks are enabled in the current product flavour and this is the app's
* launcher activity then the HockeyApp update manager is triggered and checks for updates in the background.
* If there is a new update, an alert dialog is shown and if the user presses Show,
* they will be taken to the update activity.
*/
@Override
protected void onResume() {
super.onResume();
if (BuildConfig.ENABLE_HA_CRASH_REPORTS) {
CrashManager.register(ActivityBase.this);
}
if (BuildConfig.ENABLE_HA_UPDATES && isLauncherActivity()) {
UpdateManager.register(ActivityBase.this);
}
}
/**
* If update checks are enabled in the current product flavour and this is the app's launcher
* activity then the HockeyApp update manager is unregistered.
*/
@Override
protected void onPause() {
super.onPause();
if (BuildConfig.ENABLE_HA_UPDATES && isLauncherActivity()) {
UpdateManager.unregister();
}
}
/**
* If update checks are enabled in the current product flavour and this is the app's launcher
* activity then the HockeyApp update manager is unregistered.
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (BuildConfig.ENABLE_HA_UPDATES && isLauncherActivity()) {
UpdateManager.unregister();
}
}
/**
* Overrides the Android back button and checks to see if the current fragment implements
* {@link OnBackPressedListener} if so the {@link OnBackPressedListener#onBackPressed()} method is
* called so the {@link Fragment} can take action. If the {@link Fragment} does not consume the
* back pressed event, by returning false, super is called which takes care of popping the
* fragment backstack or finishing the activity as appropriate.
*/
@Override
public void onBackPressed() {
boolean handled = false;
Fragment fragment = getSupportFragmentManager().findFragmentById(getContainerViewId());
if (fragment != null && fragment instanceof OnBackPressedListener) {
handled = ((OnBackPressedListener) fragment).onBackPressed();
}
if (!handled) {
super.onBackPressed();
}
}
/**
* Returns the current {@link Fragment} held within the container view ID if one exists.
*
* @return Current {@link Fragment} instance if one exists
*/
@Nullable
protected Fragment getCurrentFragment() {
return getSupportFragmentManager().findFragmentById(getContainerViewId());
}
/**
* Returns an array of the animation resources to run when the {@link Fragment}s that are
* entering and exiting in a transaction.
*
* This method can be overridden by implementing classes to provide a specific set of animations,
* on an activity by activity basis but provides a default set of animations.
*
* @return Array of animation resources
*/
@NonNull
protected int[] getAnimationIntArray() {
return new int[]{ENTER_ANIMATION, EXIT_ANIMATION, POP_ENTER_ANIMATION, POP_EXIT_ANIMATION};
}
/**
* Finds a container with the containerViewId 'R.id.content_fragment' and replaces any content
* with the provided {@link Fragment} instance. It handles checking to ensure that the provided
* {@link Fragment}is not identical to the current {@link Fragment}, checking bundles if necessary.
*
* @param fragment {@link Fragment} to load into the container
* @param addToBackStack True if the {@link Fragment} should be added to the activity backstack
* @param animate True if the {@link Fragment} should animate into and out of view
*/
protected void swapFragment(@NonNull Fragment fragment, boolean addToBackStack, boolean animate) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment currentFragment = fragmentManager.findFragmentById(getContainerViewId());
if (currentFragment != null && currentFragment.getClass().equals(fragment.getClass())) {
// Check the fragment arguments, if the arguments are the same then yield
if (areBundlesEqual(currentFragment.getArguments(), fragment.getArguments())) {
return;
}
}
if (animate) {
int[] anims = getAnimationIntArray();
fragmentTransaction.setCustomAnimations(anims[0], anims[1], anims[2], anims[3]);
}
fragmentTransaction.replace(getContainerViewId(), fragment);
if (addToBackStack) {
fragmentTransaction.addToBackStack(null);
}
fragmentTransaction.commit();
}
/**
* Returns true if the provided {@link Bundle} objects are equal. This is done by first checking
* for nullity, then checking the sizes of the bundles and yielding if they're not equal.
*
* If the bundle arguments contain further bundle objects then recursion is used, otherwise as a
* value for a key can be null, both values are checked in both bundles and then compared.
*
* @param args1 {@link Bundle} Arguments to compare
* @param args2 {@link Bundle} Arguments to compare
* @return True if both bundles are equivalent, else false
*/
private boolean areBundlesEqual(@Nullable Bundle args1, @Nullable Bundle args2) {
if (args1 == null && args2 == null) {
return true;
} else if (args1 == null) {
return false;
} else if (args2 == null) {
return false;
}
if (args1.size() != args2.size()) {
return false;
}
Set<String> setOne = args1.keySet();
Object valueOne;
Object valueTwo;
for(String key : setOne) {
valueOne = args1.get(key);
valueTwo = args2.get(key);
if(valueOne instanceof Bundle && valueTwo instanceof Bundle &&
!areBundlesEqual((Bundle) valueOne, (Bundle) valueTwo)) {
return false;
} else if(valueOne == null) {
if(valueTwo != null || !args2.containsKey(key)) {
return false;
}
} else if(!valueOne.equals(valueTwo)) {
return false;
}
}
return true;
}
/**
* Returns true if the current activity is a launcher activity for the application. This is
* defined in the Android Manifest using the android.intent.category.LAUNCHER intent filter category.
*
* @return True if this activity is a launcher
*/
private boolean isLauncherActivity() {
Intent intent = getIntent();
String action = intent.getAction();
Set<String> categories = intent.getCategories();
if (categories == null) categories = Collections.emptySet();
return Intent.ACTION_MAIN.equals(action) && categories.contains(Intent.CATEGORY_LAUNCHER);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment