How to add a splash screen to my android tv app ? - android

does anyone know how to add a splash screen to an Android TV App ? what is making it hard for me is that the main activity should have Theme.Leanback in order to be accepted in the google play , and to have a splash screen you need your own style/theme. So how to do this ?

You can customise Leanback's OnboardingFragment slightly to display it as splash screen. OnboardingFragment allows you to add on-boarding steps but if you don't need them you can just set setLogoResourceId inside onCreateView.
Note that it crashes if you keep page count to zero so keep page count to one and splash duration greater than LOGO_SPLASH_PAUSE_DURATION_MS = 1333 otherwise a page with "Get Started" button will be displayed.
The idea is to use onboarding fragment with just a splash screen initially and add on-boarding steps as your application grows.
OnboardingFragment
public class OnboardingFragment extends android.support.v17.leanback.app.OnboardingFragment {
private static final long SPLASH_DURATION_MS = 2000;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
if (view != null) {
view.setBackgroundColor(Color.RED);
}
setLogoResourceId(R.drawable.logo);
Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
onFinishFragment();
}
};
handler.postDelayed(runnable, SPLASH_DURATION_MS);
return view;
}
#Override
protected void onFinishFragment() {
super.onFinishFragment();
// Our onboarding is done
// Let's go back to the MainActivity
getActivity().finish();
}
#Override
protected int getPageCount() {
return 1;
}
#Override
protected String getPageTitle(int pageIndex) {
return null;
}
#Override
protected String getPageDescription(int pageIndex) {
return null;
}
#Nullable
#Override
protected View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container) {
return null;
}
#Nullable
#Override
protected View onCreateContentView(LayoutInflater inflater, ViewGroup container) {
return null;
}
}
OnboardingActivity
/*
* OnboardingActivity for OnboardingFragment
*/
public class OnboardingActivity extends Activity {
/**
* Called when the activity is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.onboarding);
}
}
onboarding.xml
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/onboarding_fragment"
android:name="com.example.android.tvleanback.ui.OnboardingFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Declare OnboardingActivity inside AndroidManifest
<activity android:name=".ui.OnboardingActivity"
android:enabled="true"
android:exported="true"
android:theme="#style/Theme.Leanback.Onboarding" />
Start OnboardingActivity from MainActivity's onCreate

Visit following site and copy code from there and make changes accorting to your activity name,img name.also make changes on your manifest to start app with spash screen
http://www.coderefer.com/android-splash-screen-example-tutorial/

Related

How to attach a fragment onto a context (programmatically)?

Currently I'm coding an android project using Android Studio 3.1.2 and SDK 19.
When I refactored almost my whole code and replaced a lot of getContext() calls with requireContext() and getActivity() with requireActivity() i came across the problem, that the app crashes already at the launcher activity. I know that there are several posts related to the same problem of getting IllegalStateException: Fragment myFragment not attached to a contextbut they're all very project-specific so it doesn't actually show me the step i missed to do. So i hereby show you my example of code and pray for a merciful programmer that enlightens me, what I have to do, to solve this problem just in the suiting way.
This is my SplashActivity (the launcher activity):
public class SplashActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
Fragment fragmentToDisplay = null;
if (!(getIntent().getBooleanExtra("isLaunch", true))) {
fragmentToDisplay = new LoginFragment();
} else {
if (savedInstanceState == null) {
fragmentToDisplay = new SplashFragment();
}
}
if (fragmentToDisplay.isAdded()) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragmentToDisplay).commit();
}
}
}
This is the SplashFragment which gets loaded initially:
public class SplashFragment extends RequestingFragment {
private Handler delayHandler = new Handler();
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View fragmentView = inflater.inflate(R.layout.fragment_splash, container, false);
requestQueue = Volley.newRequestQueue(this.requireContext());
requestParams.add(SessionHandler.getAppInstanceID(this.getContext()));
startRequest(RequestOperation.SESSION_CHECK);
onSuccess(new JSONObject(), "");
return fragmentView;
}
#Override
public void onDestroy() {
super.onDestroy();
delayHandler.removeCallbacksAndMessages(null);
}
#Override
public void onSuccess(final JSONObject json, String parsingKey) {
delayHandler.postDelayed(new Runnable() {
#Override
public void run() {
//parsing stuff
}
}, 2000);
}
#Override
public void onError() {
showErrorDialog();
}
private void showErrorDialog() {
//show a horrifying dialog
}
}
I would be very thankful, if someone could explain to me, what in particular is causing the exception and how do I do it correctly. Thanks in advance.

Why does pressing back from detail activity after landscape-to-portrait-switch show an empty screen?

Below is the MainActivity class that I'm using. The code checks to see if the phone is in landscape or portrait. If it's in portrait, it will show the main fragment in the main activity only (the main fragment is a static fragment in the main_activity.xml file). Then if a "Recipe" is clicked it will open a detail activity with its own fragment. If the phone is in landscape mode, it will show the main fragment and the detail fragment side by side. Everything works perfectly fine however when I follow the procedure below I get a white screen instead of the main activity:
Procedure:
Switch to landscape
Switch back to portrait
Choose an item and wait for the detail activity to open
Press back
Here instead of the main activity window I get a white screen
If I don't switch to landscape and just start with the portrait mode everything is fine. It seems like switching to landscape does something that causes the problem and I can't figure out what. Any tip on what's going on or where to look would be much appreciated.
public class MainActivity extends AppCompatActivity implements RecipesFragment.OnRecipeClickListener {
private String RECIPE_PARCEL_KEY;
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RECIPE_PARCEL_KEY = getString(R.string.ParcelKey_RecipeParcel);
if (findViewById(R.id.linearLayoutTwoPane) != null) {
mTwoPane = true;
if (savedInstanceState == null) {
RecipeFragment recipeFragment = new RecipeFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.recipeFrameForTwoPane, recipeFragment)
.commit();
}
} else {
mTwoPane = false;
}
}
#Override
public void OnRecipeClick(Recipe recipe) {
if (mTwoPane) {
RecipeFragment recipeFragment = new RecipeFragment();
recipeFragment.setRecipe(recipe);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.recipeFrameForTwoPane, recipeFragment)
.commit();
} else {
Class destinationClass = DetailActivity.class;
Intent intentToStartDetailActivity = new Intent(this, destinationClass);
intentToStartDetailActivity.putExtra(RECIPE_PARCEL_KEY, recipe);
startActivity(intentToStartDetailActivity);
}
}
}
EDIT:
Adding RecipeFragment's code below:
public class RecipeFragment extends Fragment {
private Recipe mRecipe;
#BindView(R.id.tv_recipeName) TextView recipeNameTextView;
public RecipeFragment(){
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recipe_fragment,container,false);
ButterKnife.bind(this,view);
if(mRecipe!=null) {
recipeNameTextView.setText(mRecipe.getName());
}else{
recipeNameTextView.setText(getString(R.string.messageSelectARecipe));
}
return view;
}
public void setRecipe(Recipe recipe){
mRecipe = recipe;
}
}
EDIT:
I followed #mt0s's advice and created different background colors for the fragments and activities and finally narrowed down the problem to a line in my recyclerview adapter code. My adapter code is below. Inside loadInBackground() on line URL url = new URL(getString(R.string.URL_RecipeJSON)); I get a Fragment RecipesFragment{96e9b6a} not attached to Activity exception. I don't understand why I'm getting this exception and what the best way to resolve this is. Have I placed the right code in the right fragment methods (ie OnCreate vs OnActivityCreated vs OnCreateView vs etc)?
public class RecipesFragment extends Fragment
implements RecipeAdapter.RecipeAdapterOnClickHandler,
LoaderManager.LoaderCallbacks<ArrayList<Recipe>> {
#BindView(R.id.rv_recipes) RecyclerView mRecyclerView;
private RecipeAdapter mRecipeAdapter;
private static final int LOADER_ID = 1000;
private static final String TAG = "RecipesFragment";
private OnRecipeClickListener mOnRecipeClickListener;
public RecipesFragment(){
}
public interface OnRecipeClickListener {
void OnRecipeClick(Recipe recipe);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recipes_fragment, container, false);
ButterKnife.bind(this, view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(true);
mRecipeAdapter = new RecipeAdapter(this);
mRecyclerView.setAdapter(mRecipeAdapter);
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public void onPause() {
super.onPause();
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void OnClick(Recipe recipe) {
mOnRecipeClickListener.OnRecipeClick(recipe);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try{
mOnRecipeClickListener = (OnRecipeClickListener) context;
} catch (ClassCastException e){
Log.e(TAG, "onAttach: Host activity class must implement OnRecipeClickListener.");
}
}
#Override
public Loader<ArrayList<Recipe>> onCreateLoader(int i, Bundle bundle) {
return new AsyncTaskLoader<ArrayList<Recipe>>(getActivity()) {
#Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
#Override
public ArrayList<Recipe> loadInBackground() {
String response;
ArrayList<Recipe> recipes = null;
try {
URL url = new URL(getString(R.string.URL_RecipeJSON)); //***I get an exception here***
response = NetworkUtils.getResponseFromHttpUrl(url, getActivity());
recipes = RecipeJsonUtils.getRecipeFromJson(getActivity(), response);
} catch (Exception e) {
Log.e(TAG, "loadInBackground: " + e.getMessage());
}
return recipes;
}
};
}
#Override
public void onLoadFinished(Loader<ArrayList<Recipe>> loader, ArrayList<Recipe> recipes) {
mRecipeAdapter.setRecipeData(recipes);
}
#Override
public void onLoaderReset(Loader<ArrayList<Recipe>> loader) {
}
}
I finally figured out the problem and the solution. The problem is that onStartLoading() in the AsyncTaskLoader anonymous class in RecipesFragment class gets called every time the fragment is resumed whether the enclosing Loader is called or not. This causes the problem. I need to have control over when onStartLoading() is being called and I only want it to be called if and only if the enclosing Loader is being initialized or restarted. As such, I destroyed the loader in onPause() of the fragment and restarted it in onResume(). Hence, I added the following code to the RecipesFragment class:
#Override
public void onPause() {
super.onPause();
getLoaderManager().destroyLoader(LOADER_ID);
}
#Override
public void onResume() {
super.onResume();
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
I also removed initLoader() from onCreate(). This way, every time the fragment is resumed (or created) onStartLoading() will be called. I tried this and it solves my problem.
When you switch from the landscape to portrait or the opposite the Android OS destroy your activity and recreate it again. this what probably trigger your problem

"Already executed" error (Retrofit2 and Android)

First of all, I have 2 fragments and they show different json values(2 links). If I change fragment(.replace()), that fragment get values with retrofit and show it. It works well but other fragment is deleted with values. After change fragment, it downloads again so I change structure. I want to take 2 json objects once so I get json objects in mainactivity and fragments get these with methods. They work well in first opening but if i open a fragment second time, it gives this error. How can I solve it?
java.lang.IllegalStateException: Already executed.
at retrofit2.OkHttpCall.enqueue(OkHttpCall.java:84)
Code is very long, i will show main structure.
MainActivity.java
public class MainActivity extends AppCompatActivity
{
private Call<Restaurant[]> restaurantCall;
private Call<Dining[]> diningCall;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Restaurant Json value
RestaurantInterface restaurantInterface = RetroClient.getClient().create(RestaurantInterface.class);
restaurantCall = restaurantInterface.getJsonValues();
//Dininghall Json value
DiningInterface diningInterface = RetroClient.getClient().create(DiningInterface.class);
diningCall = diningInterface.getJsonValues();
}
public Call<Restaurant[]> RestaurantJson()
{
return this.restaurantCall;
}
public Call<Dining[]> DiningJson()
{
return this.diningCall;
}
}
RestaurantFragment.java (Other fragment has same structure)
public class RestFragment extends Fragment
{
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_rest, container, false);
Call<Restaurant[]> call = ((MainActivity) getActivity()).RestaurantJson();
call.enqueue(new Callback<Restaurant[]>()
{
.
.
Summing up, in order to avoid the "Already executed" exception, clone the call:
public Call<Restaurant[]> RestaurantJson()
{
return this.restaurantCall.clone();
}
public Call<Dining[]> DiningJson()
{
return this.diningCall.clone();
}
If you want to execute the call in the activity and not in the fragment as you are actually doing, then you need to call enqueue(new Callbak(... in your activity.
So you need something like this:
public class RestFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_rest, container, false);
((MainActivity) getActivity()).RestaurantJson(this);
}
public void onResponse(Response<T> response) {
//your fragment code on response
}
...
}
public class MainActivity extends AppCompatActivity {
public void RestaurantJson(final RestFragment fragment)
{
RestaurantInterface restaurantInterface = RetroClient.getClient().create(RestaurantInterface.class);
restaurantCall = restaurantInterface.getJsonValues();
restaurantCall.enqueue(new Callback<Restaurant[]>() {
#Override
public void onResponse(Call<T> call, Response<T> response) {
...
fragment.onResponse(response);
}
}
...
}
The same for your DiningFragment and your dining call...

Android dynamic fragment loading progress bar visibility won't set

I'm using Android fragments to load the code in my application. To create a simple loader I have LoadingFragment extends Fragment and then my fragment classes extend that, for example: MyFragment extends LoadingFragment.
The LoadingFragment has hideLoader and showLoader which theoretically my subclass Fragment should be able to call in onCreateView and onPostExecute to show and hide a progress bar in between loads.
Layout wise I have a main_layout.xml which has a framelayout for the dynamic fragments and a static relativelayout that houses the progress bar.
At the moment my fragments load and replace from within one another onclick of elements but I have removed that code.
The Problem
The issue is the setVisibilty in LoadingFragment seems to have zero effect when calling it from the subclasses, for example MyFragment, why is this?
I have given LoadingFragment it's own View variable of viewMaster which I believe should reference main_layout but still the visibility changes seem to have no effect?
MainActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyFragment myFragment = MyFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_holder, myFragment).commit();
}
}
LoadingFragment
public class LoadingFragment extends Fragment {
View viewMaster;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
viewMaster = inflater.inflate(R.layout.main_layout, container, false);
return viewMaster;
}
public void showLoader() {
viewMaster.findViewById(R.id.fragment_holder).setVisibility(View.INVISIBLE);
viewMaster.findViewById(R.id.loading).setVisibility(View.VISIBLE);
}
public void hideLoader() {
viewMaster.findViewById(R.id.fragment_holder).setVisibility(View.VISIBLE);
viewMaster.findViewById(R.id.loading).setVisibility(View.INVISIBLE);
}
}
MyFragment
public class MyFragment extends LoadingFragment {
View view;
public MyFragment() {}
public static MyFragment newInstance() {
MyFragment fragment = new MyFragment();
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
super.showLoader();
view = inflater.inflate(R.layout.fragment_lans, container, false);
MyFragment.ApiCallJob apicalljob = new MyFragment.ApiCallJob();
apicalljob.execute("a string");
return view;
}
public class ApiCallJob extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String[] params) {
// do things
}
#Override
protected void onPostExecute(String data) {
// do things
// tried both to be sure but they should do the same?
hideLoader();
MyFragment.super.hideLoader();
}
}
}
main_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="app.stats.MainActivity"
android:orientation="horizontal">
<FrameLayout
android:id="#+id/fragment_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<RelativeLayout
android:id="#+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/progress_bar"/>
</RelativeLayout>
</RelativeLayout>
The issue is the setVisibility in LoadingFragment seems to have zero effect when calling it from the subclasses, for example MyFragment, why is this?
Let's look at what happens when MyFragment onCreateView is called:
You call super onCreateView which is in LoadingFragment. This inflates main_layout and returns the inflated view. Note that this view is not referenced in MyFragment (i.e. View mainLayout = super.onCreateView(inflater, container, savedInstanceState);)
You just inflated the view that has loading view group, then threw it away. However, you did manage to save a reference to that view in the LoadingFragment superclass.
Next you inflate R.layout.fragment_lans and (after starting your load) you return that view as the view to be used in the fragment.
So the thing to observe here is that LoadingFragment now has a reference to a view that is nowhere in the fragment's active view hierarchy.
Given that, it's no wonder that setVisibility doesn't do anything, because the fragment isn't displaying that view.
I wouldn't use inheritance to do this, but if you must use it, here's how to fix your problem.
Let's just use the ProgressBar without the view group wrapper. Since it's in a RelativeLayout, let's center it. Then make it android:visibility="gone" so it's effectively out of the fragment layout:
<ProgressBar
android:id="#+id/progress_bar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>
You're going to put this progress bar in the layout for every LoadingFragment subclass, including fragment_lans.xml. You can use the <include> tag to make life easier here.
Change the LoadingFragment so that a) it doesn't interfere with the inflation of its subclass' layouts and b) it uses the progress bar in the subclass' active view hierarchy:
public class LoadingFragment extends Fragment {
public void showLoader() {
if (getView() != null) {
View progressBar = getView().findViewById(R.id.progress_bar);
if (progressBar != null) {
progressBar.setVisibility(View.VISIBLE);
}
}
}
public void hideLoader() {
if (getView() != null) {
View progressBar = getView().findViewById(R.id.progress_bar);
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
}
}
}
}
Now you should see what you expect.
EDIT
If you really want to use the layout scheme you started with, here is my recommendation:
Your activity is inflating main_layout that has the progress bar, so I would give some of the responsibility to the activity.
Now, you could just write some code for the fragment that would trawl around the view hierarchy and look for the progress bar. That wouldn't be my preference.
Here's another approach:
Create an interface
interface ProgressDisplay {
public void showProgress();
public void hideProgress();
}
Make the activity implement it
public class MainActivity extends AppCompatActivity implements ProgressDisplay {
...
public void showProgress() {
findViewById(R.id.loading).setVisibility(View.VISIBLE);
}
public void hideProgress() {
findViewById(R.id.loading).setVisibility(View.GONE);
}
Then add the methods to LoadingFragment that call the activity:
protected void showProgress() {
if (getActivity() instanceof ProgressDisplay) {
((ProgressDisplay) getActivity()).showProgress();
}
}
protected void hideProgress() {
if (getActivity() instanceof ProgressDisplay) {
((ProgressDisplay) getActivity()).hideProgress();
}
}
This way you can take any activity that inflates a layout with your progress view and have it implement ProgressDisplay so you can use the LoadingFragment class with it.
You could also make a LoadingActivity class and subclass that, too.
You calling setVisibility via showLoader on View which is not used to draw on screen becouse you returning other View as fragment view.
Something like that should work (MyFragment):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
ViewGroup vg = (ViewGroup) v.findViewById(R.id.fragment_holder)
inflater.inflate(R.layout.fragment_lans, vg, true);
MyFragment.ApiCallJob apicalljob = new MyFragment.ApiCallJob();
showLoader();
apicalljob.execute("a string");
return v;
}
If you are using inheritance then you cant assign layout to parent fragment.
You can take advantage of inheritance for View Binding with butterknife like this and write much clean code.
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.emerson.techdayapp.R;
import com.emerson.techdayapp.view.activity.MainActivity ;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
/**
* Base fragment created to be extended by every fragment in this application. This class provides
* dependency injection configuration, ButterKnife Android library configuration and some methods
* common to every fragment.
*/
public abstract class BaseFragment extends Fragment {
private Unbinder unbinder;
#BindView(R.id.htab_toolbar)
Toolbar mToolbar;
#BindView(R.id.loading_view)
ProgressBar loadingView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(getFragmentLayout(), container, false);
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//bind view
injectViews(view);
//set page title
setUpToolBar(getPageTitle());
//add click listeners
setUpViewEvents();
//make service call
fillData();
}
/**
* #return resource if of fragment layout
*/
protected abstract int getFragmentLayout();
/**
* Set toolbar title
*
* #return
*/
protected abstract String getPageTitle();
/**
* Add click listeners
*/
protected abstract void setUpViewEvents();
/**
* Make Web serve calls here
*/
protected abstract void fillData();
/**
* Inject View
*
* #param view
*/
private void injectViews(final View view) {
unbinder = ButterKnife.bind(this, view);
}
/**
* Set up toolbar
*
* #param pageTitle
*/
private void setUpToolBar(String pageTitle) {
getActivity().setTitle(pageTitle);
if (mToolbar != null) {
((MainActivity ) getActivity()).setSupportActionBar(mToolbar);
((MainActivity ) getActivity()).getSupportActionBar()
.setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationIcon(R.drawable.ic_drawer);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//TODO what to do on back press
}
});
mToolbar.setTitleTextColor(Color.WHITE);
}
}
public void showProgressDialog() {
if (null != loadingView)
loadingView.setVisibility(View.VISIBLE);
}
public void dismissProgressDialog() {
if (null != loadingView)
loadingView.setVisibility(View.GONE);
}
#Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}
And then use base fragment like this
public class MyFragment extends BaseFragment {
public MyFragment() {
}
public static MyFragment newInstance() {
MyFragment fragment = new MyFragment();
return fragment;
}
#Override
protected int getFragmentLayout() {
//put your frag layout id here
return R.layout.frag_pulse;
}
#Override
protected String getPageTitle() {
//Add some title to page
return "Some Title";
}
#Override
protected void setUpViewEvents() {
}
#Override
protected void fillData() {
new AsyncTask<String, Void, String>() {
#Override
protected void onPreExecute() {
super.onPreExecute();
//Show Progress
showProgressDialog();
}
#Override
protected String doInBackground(String... strings) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
//Hide Progress
dismissProgressDialog();
}
};
}
}
Things to note here are your fragment layout must have one toolbar id htab_toolbar and a progress bar with id loading_view as this are used by base class

Loader is not retained after starting new activity, changing orientation in new activity and pressing back button

I been running into an issue with loaders lately. I created a small project that reproduces the issue https://github.com/solcott/loaders-orientation-change
I have a simple Activity that adds a fragment
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
if(mainFragment == null){
Log.w(getClass().getSimpleName(), "creating new Fragment");
mainFragment = new MainFragment();
getFragmentManager().beginTransaction().add(R.id.main_fragment, mainFragment).commit();
}
}
}
In my fragment I start a Loader that just returns an integer that is displayed on the screen. There is also a button that starts a new Activity:
public class MainFragment extends Fragment implements LoaderCallbacks<Integer> {
private static int NEXT_VAL = 0;
TextView text1;
Button button1;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(1, null, this);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
text1 = (TextView) view.findViewById(android.R.id.text1);
button1 = (Button) view.findViewById(android.R.id.button1);
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(getActivity(), NextActivity.class));
}
});
}
#Override
public Loader<Integer> onCreateLoader(int id, Bundle args) {
AsyncTaskLoader<Integer> loader = new AsyncTaskLoader<Integer>(
getActivity()) {
#Override
public Integer loadInBackground() {
return NEXT_VAL++;
}
};
loader.forceLoad();
return loader;
}
#Override
public void onLoadFinished(Loader<Integer> loader, Integer data) {
text1.setText(data.toString());
}
#Override
public void onLoaderReset(Loader<Integer> loader) {
// TODO Auto-generated method stub
}
}
Up to this point everything works fine I can change the orientation and the integer that is displayed doesn't change. I can even start the new activity and then hit the back button and the integer displayed doesn't change.
However, when I navigate to the new Activity, change orientation and press the back button the integer is incremented. I would expect it to behave the same way that it did without the orientation change.
Calling setRetainInstance(true) in Fragment.onCreate() make it even worse. onLoadComplete is never called and the integer is not displayed at all.
Has anyone else run into this issue and found a workaround?
Same question as asked here: Loader unable to retain itself during certain configuration change
This is a damn annoying problem with AsyncTask loaders. Although a solution is proposed in that thread, I think the general consensus is to avoid AsyncTaskLoaders.

Categories

Resources