I'm new in android. I have a code which is consisted ViewPager and TabLayout. And it has 4 Fragments. On my Fragment4 code, I want to use CircleProgressView which I got in GitHub. To use that code, I should use AsyncTask. And it has "MainActivity.this.runOnUiThread(new Runnable){}". But this code occurred error. The message is "MainActivity is not an enclosing class." I think it means MainActivity class could not be referred. How can I call "MainActivity.this.runOnUiThread()"? Please tell me how to fix code.
Here is a part of my code.
public class Fragment4 extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment4, container, false);
...
return rootView;
}
#Override
public void onStart() {
super.onStart();
}
private class LongOperation extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
mCircleView.setValue(0);
mCircleView.spin();
}
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
mCircleView.setValueAnimated(42);
}
}
}
MainActivity.this is just a way to get the Activity context, however there are multiple other ways to obtain this context.
Use getActivity() to get the context from the current Activity
Use getApplicationContext(), which is a different context which is the same for all activities but should work here too.
If you use API 23 or above you can use getContext() from within a Fragment to get the current context.
Use getActivity() instead of MainActivity.this
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mCircleView.setValue(0);
mCircleView.spin();
}
});
Related
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.
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
So question. Which of the two is the proper way to do a callback?(Unless both of these are incorrect) Or does it depend on the case.
For example. Let's say i wanted to return something from a Fragment to its Activity.
1)I implement an interface in the Fragment and then on its onAttach I set the listener, and then implement the interface in the activity.
public class UrgentCareFragment{
public interface TestListener {
void finishedTest();
}
TestListener mEventListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_urgent_care_viewpager_fragment, container, false);
mEventListener.finishedTest();
return v;
}
#TargetApi(23)
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mEventListener = (TestListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement urgentCareListener");
}
}
/*
* Deprecated on API 23
* Use onAttachToContext instead
*/
#SuppressWarnings("deprecation")
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (Build.VERSION.SDK_INT < 23) {
try {
mEventListener = (TestListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement urgentCareListener");
}
}
}
}
Activity Class.
public class testActivity extends AppCompatActivity implements TestListener{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_urgent_care);
}
#Override
public void finishedTest(){
//do what you need to do
}
}
2) I create an interface class. In the Fragment I set the listener to the activities context by using getActivity. And then implement the interface in the activity.
Interface Class
public interface TestListener {
void finishedTest();
}
Fragment
public class UrgentCareFragment{
TestListener mEventListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mEventListener = (TestListener) getActivity();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_urgent_care_viewpager_fragment, container, false);
mEventListener.finishedTest();
return v;
}
}
Activity Class.
public class testActivity extends AppCompatActivity implements TestListener{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_urgent_care);
}
#Override
public void finishedTest(){
//do what you need to do
}
}
Which is the correct way? My main concern is the way the listener is set. Could either of these two cause issues. Or is there one i should throw out. Both seem to work properly, I am just trying to make sure what the correct way is, or if both are fine.
Thanks
Hi there I'm thinking about what is the correct and best way to handle Activity, Fragment, AsyncTask and DialogFragments together.
My current state is that I start my Activity and replace its ContentView with my Fragment, in which I got an EditText and one Button.
Tapping my Button executes an AsyncTasks which Requests random things and takes some time. Meanwhile I display a DialogFragment begging for patience.
Desired behavior is that, e.g. I rotate my screen my DialogFragment keeps being displayed for the time my AsyncTask is running. After that I want to show up a simple toast displaying the information I got from my HttpRequest.
Compact overview about how I thought it would work:
BaseFragment keeps a WeakReference to the Activity it's attached to
AsyncTask keeps a WeakReference to Fragment which exectures it
AsyncTasks onPreExecute() shows up the DialogFragment
AsyncTasks onPostExecute() dissmisses the DialogFragment
BaseFragment holds DialogFragment
Unfortunately this is not the way it works, on orientation change my DialogFragment keeps being displayed and no toast is showing up.
What am I doing wrong ?
public class BaseFragment extends Fragment{
private static final String TAG = BaseFragment.class.getSimpleName();
protected WeakReference<AppCompatActivity> mActivity;
private TemplateDialogFragment dialogFragment;
public WeakReference<AppCompatActivity> getAppCompatActivity(){ return mActivity; }
#Override
public void onAttach(Context context) {
if(!(context instanceof AppCompatActivity)) {
throw new IllegalStateException(TAG + " is not attached to an AppCompatActivity.");
}
mActivity = new WeakReference<>((AppCompatActivity) context);
super.onAttach(context);
}
#Override
public void onDetach() {
mActivity = null;
super.onDetach();
}
#Override
public void onStart() {
super.onStart();
showContent();
}
public void showContent(){
}
public void showDialog(String title, String content){
dialogFragment = new TemplateDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(TemplateDialogFragment.DIALOG_TITLE, title);
bundle.putString(TemplateDialogFragment.DIALOG_MESSAGE, content);
dialogFragment.setArguments(bundle);
dialogFragment.show(getFragmentManager(), TemplateDialogFragment.FRAGMENT_TAG);
}
public void notifyTaskFinished(String result) {
dismissDialog();
if(mActivity != null && !mActivity.get().isFinishing()) {
Toast.makeText(mActivity.get().getApplicationContext(), result, Toast.LENGTH_LONG).show();
}
}
private void dismissDialog(){
if(dialogFragment != null && dialogFragment.isAdded()) {
dialogFragment.dismissAllowingStateLoss();
}
}
}
...
public class TemplateFragment extends BaseFragment {
private static final String TAG = TemplateFragment.class.getSimpleName();
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.test_fragment, container, false);
}
#Override
public void showContent() {
super.showContent();
Button startTask = (Button) getAppCompatActivity().get().findViewById(R.id.button0);
final BaseFragment instance = this;
startTask.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
CustomAsyncTask task = new CustomAsyncTask(instance);
EditText input = (EditText) getAppCompatActivity().get().findViewById(R.id.text0);
task.execute(input.getText().toString());
}
});
}
private static class CustomAsyncTask extends AsyncTask<String, Void, String> {
WeakReference<BaseFragment> weakBaseFragmentReference;
private CustomAsyncTask(BaseFragment fragment) {
weakBaseFragmentReference = new WeakReference<>(fragment);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
weakBaseFragmentReference.get().showDialog("Executing", "Working on the request...");
}
#Override
protected String doInBackground(String... params) {
HttpURLConnection con = HttpUrlConnectionFactory.createUrlConnection("https://www.httpbin.org/bytes/" + (params[0] == null ? "1" : params[0]));
return HttpRequester.doGet(con).getResponseAsString();
}
#Override
protected void onPostExecute(String response) {
super.onPostExecute(response);
if(weakBaseFragmentReference.get() == null) {
return;
}
weakBaseFragmentReference.get().notifyTaskFinished(response);
}
}
}
*Edit:
After some time researching this theme I'm sure a Service is the best solution for most of my field of use. Also I used AsyncTaskLoaders a lot, because there is a smooth control of lifecycle....
Use progress bar instead of DialogFragment.
AsyncTask should only be used for tasks that take quite few seconds.
AsyncTask doesn't respect Activity lifecycle, and can lead to memory leaks.
Check some gotchas.
You can try AsyncTaskLoader to survive configuration changes.
Like my title says, i'm looking for an equivalent of getActivity() in my ActionBarActivity class in my Android project.
I want to pass an Activity parameter in AsyncTask declaration object, because i'm using an Activity object in my custom AsyncTask extended class
Here an example simplest code of my project
public class EventCreator extends ActionBarActivity {
private Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_even_creator);
View v = getLayoutInflater().inflate(R.layout.activity_even_creator,null);
this.context = this.getBaseContext();
final Button createButton = (Button)findViewById(R.id.createEventButton);
createButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
AsyncTask<Void,Void,Boolean> eventCreatorSend = new SendEvents(/* here need activity object */);
eventCreatorSend.execute();
}
});
}
class SendEvents extends AsyncTask<Void,Void,Boolean> {
public Activity act;
SendEvents(Activity a) {
this.act = a;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
((LinearLayout)act.findViewById(R.id.layout_loader_create_event)).setVisibility(View.VISIBLE);
}
#Override
protected Boolean doInBackground(Void... params) {
SystemClock.sleep(5000);
return true;
}
#Override
protected void onPostExecute(Boolean params) {
if (params){
((LinearLayout)act.findViewById(R.id.layout_loader_create_event)).setVisibility(View.GONE);
act.finish();
}
else {
((LinearLayout)act.findViewById(R.id.layout_loader_create_event)).setVisibility(View.VISIBLE);
Toast.makeText(act,"Fail to send event",Toast.LENGTH_SHORT).show();
}
}
};
}
In a time, i thought use getParent() from ActionBarActivity class, but it return a null object.
So how to get the Activity object i want in ActionBarActivity class ?
Try nameofactivity.this instead getActivity()
I always use getActivity() in Fragments Activities and .this in any other kind of Activity.
Oh damn !
I just found a solution just after posting my ask.
I use MyClass.this, and it's done. Like this :
AsyncTask<Void,Void,Boolean> eventCreatorSend = new SendEvents(EventCreator.this);
eventCreatorSend.execute();
Hope that's can help someone !
The easiest way is an Activity variable
// in your fragment
Activity myActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
//fragment shouts "i know my father!!!"
myActivity = activity; //
}
now your activity instance(The father) can be represented anywhere