execute a method very time fragment appears - java

I'm trying to make an method executable everytime the fragment appears. I have a MainActivity and a PagerAdapter and inside PagerAdapter, I have Fragments so my problem is that I placed a method inside my onCreate of Fragment but it's executing only once. Maybe the onCreate of Activity and Fragment both have roles here. After searching about a similar question I found this :
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
// Do your Work
} else {
// Do your Work
}
}
But when I am putting my method here am getting a NullPointerException. My method:
public void retrieveLocalStoredNotes() {
notesArray = new ArrayList<String>();
notesIDArray = new ArrayList<String>();
for (NotesRealmClass note : NotesQueryRealm) {
notesArray.add(note.getTitle());
notesIDArray.add(note.getobjectId());
}
notesAdapter = new NotesAdapter(getActivity(), getData());
recyclerView.setAdapter(notesAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
}
Maybe am getting this error because I am initialising the variables in onCreate and setVisibleHint and it is running before onCreate. How can I make this workable or is my approach not enough for that.

Call your method in onResume() method.

Related

Android Studio "not attached to context" errors [duplicate]

In activity in Toolbar I got a button which need to call method from fragment and update list in that fragment. Now it is an error.
Calling in activity
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.menu_sort:
ListFragment listFragment = new ListFragment();
listFragment.sortByPopularity();
break;
}
return super.onOptionsItemSelected(item);
}
Fragment code. I have found an error when Activity not attached. But nothing with context
public class ListFragment extends Fragment implements ListAdapter.ItemClickListener {
/**
* Needed
*/
RecyclerView recyclerView;
View view;
List<BasePojo.Result> list;
ListAdapter listAdapter;
public ListFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
/**
* Main Initialization
*/
view = inflater.inflate(R.layout.fragment_list, container, false);
recyclerView = view.findViewById(R.id.recycler_list_detailed);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
list = new ArrayList<>();
listAdapter = new ListAdapter(list, setOnItemClickCallback());
recyclerView.setAdapter(listAdapter);
RetrofitClient.getApiService().getPhotosList(getString(R.string.api_key)).enqueue(new Callback<BasePojo>() {
#Override
public void onResponse(Call<BasePojo> call, Response<BasePojo> response) {
BasePojo basePojo = response.body();
list.addAll(basePojo.getResults());
recyclerView.getAdapter().notifyDataSetChanged();
}
#Override
public void onFailure(Call<BasePojo> call, Throwable t) {
Log.d("tag", "Response failed" + t.toString());
}
});
return view;
}
#Override
public void onItemClick(View view, int position) {
Log.v("in on click", "value " + position);
}
private OnItemClickListener.OnItemClickCallback setOnItemClickCallback() {
OnItemClickListener.OnItemClickCallback onItemClickCallback = new OnItemClickListener.OnItemClickCallback() {
#Override
public void onItemClicked(View view, int position) {
BasePojo.Result itemClicked = list.get(position);
Bundle bundle = new Bundle();
bundle.putString("title", itemClicked.getOriginalTitle());
bundle.putString("overview", itemClicked.getOverview());
bundle.putString("release_date", itemClicked.getReleaseDate());
bundle.putString("vote_average", itemClicked.getVoteAverage().toString());
bundle.putString("poster_path", itemClicked.getPosterPath());
DetailedFragment detailedFragment = new DetailedFragment();
detailedFragment.setArguments(bundle);
FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.main_frame_list, detailedFragment);
Log.d("tag", "title is 111 " + bundle.get("title"));
transaction.commit();
}
};
return onItemClickCallback;
}
#Override
public void onAttachFragment(Fragment childFragment) {
super.onAttachFragment(childFragment);
}
public void sortByPopularity() {
RetrofitClient.getApiService().getPopularList(getString(R.string.api_key)).enqueue(new Callback<BasePojo>() {
#Override
public void onResponse(Call<BasePojo> call, Response<BasePojo> response) {
BasePojo basePojo = response.body();
list.addAll(basePojo.getResults());
recyclerView.getAdapter().notifyDataSetChanged();
}
#Override
public void onFailure(Call<BasePojo> call, Throwable t) {
Log.d("tag", "Response failed" + t.toString());
}
}); }
}
And here is an error
05-09 12:48:26.915 5775-5775/com.borisruzanov.popularmovies E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.borisruzanov.popularmovies, PID: 5775
java.lang.IllegalStateException: Fragment ListFragment{6dbd6de} not attached to a context.
at android.support.v4.app.Fragment.requireContext(Fragment.java:614)
at android.support.v4.app.Fragment.getResources(Fragment.java:678)
at android.support.v4.app.Fragment.getString(Fragment.java:700)
at com.borisruzanov.popularmovies.ListFragment.sortByPopularity(ListFragment.java:110)
at com.borisruzanov.popularmovies.MainActivity.onOptionsItemSelected(MainActivity.java:47)
at android.app.Activity.onMenuItemSelected(Activity.java:3204)
at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:407)
at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:195)
at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:108)
at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:108)
at android.support.v7.app.ToolbarActionBar$2.onMenuItemClick(ToolbarActionBar.java:63)
at android.support.v7.widget.Toolbar$1.onMenuItemClick(Toolbar.java:203)
at android.support.v7.widget.ActionMenuView$MenuBuilderCallback.onMenuItemSelected(ActionMenuView.java:780)
at android.support.v7.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:822)
at android.support.v7.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:171)
at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:973)
at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:963)
at android.support.v7.widget.ActionMenuView.invokeItem(ActionMenuView.java:624)
at android.support.v7.view.menu.ActionMenuItemView.onClick(ActionMenuItemView.java:150)
at android.view.View.performClick(View.java:5610)
at android.view.View$PerformClick.run(View.java:22265)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Thank you very much for your time and help. If my question looks not well please make a note and I will teach how to ask questions better
In my case, this problem occurred when I was calling getString()
changing this calls to getActivity().getString() solved the problem.
Using commit() can not solve the problem, we should try to find the solution in the source code of Fragment.
So, consider from the error stack you provided, the requireContext() in Fragment was:
public final Context requireContext() {
Context context = getContext();
if (context == null) {
throw new IllegalStateException("Fragment " + this + " not attached to a context.");
}
return context;
}
This means the system will check the Context from getContext(), if it's null, the exception will be thrown.
So, to avoid this problem, we can check the result of getContext() before do our business.
Create a fragment instance is not enough. It needs to be attached to Activity through a transaction:
getFragmentManager()
.beginTransaction()
.replace(R.id.container_layout, fragment)
.commit();
After a successful commit, onAttach method in the fragment is called, the view is created and then you can interact with its views.
In your case, create the fragment instance and attach it in activity onCreate, then call sortByPopularity later in a click event.
Read more about fragment life cycle: https://developer.android.com/guide/components/fragments
Kotlin:
My problem happened with getString()
Changing it to context.getString() solved it
If you are using CountDownTimer, you may get that error cause of detaching the fragment before finishing the timer. If you are performing ui changes in onFinish callback, you should check the context that it is null or not like below;
timer = object : CountDownTimer(startTimeInMillis, 1000) {
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
context?.let {
//perform ui changes here
}
}
}
timer?.start()
or you should cancel the timer before detaching fragment like below;
override fun onDestroy() {
super.onDestroy()
timer?.cancel()
}
If a fragment is not shown (not added) or is removed, it's context == null. In this case getting resources will lead to this exception. getString(R.string.some_string) requires context and crashes.
You can check whether the fragment exists so:
if (isAdded) {
// Print getString(R.string.some_string).
}
But you might need to print the string even when the fragment was released, for instance, in LogCat, analytics or send a request to a server. In this case you need an application context to obtain a string resource.
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
instance = this
}
companion object {
lateinit var instance: MyApplication private set
}
}
object Strings {
fun get(#StringRes stringRes: Int, vararg formatArgs: Any = emptyArray()): String {
return instance.getString(stringRes, *formatArgs)
}
}
Then set MyApplication in AndroidManifest and use: Strings.get(R.string.some_string).
For kotlin developers
lifecycleScope.launchWhenResumed {
// do your work here
}
Kotlin : Use Lazy Initialisation
override val contentMessage by lazy {
getString(R.string.message)
}
For everybody who still have this error:
private Context mContext;
#Override
public void onAttach(Context context) {
super.onAttach(activity);
mContext = context;
}
And do not add mContext = null in onDetach method 'cause you will still get this error.
I know this is an old post, but I just figured out what you could do. It is true that creating a fragment instance is not enough, It needs to be attached to Activity through a transaction. However, you can initially add both fragments and detach them from fragment manager. That way, they are both 'alive' in fragmentManager and you can call attach and detach on those fragments later as you wish.
i.e
.add(container, fragment1).detach(fragment1).add(container, fragment2).commit();
.
.
.
.
.
ft.detach(fragment2)
ft.attach(fragment1
This assertion can occur anywhere you have a requireContext() call to get access to the Android context from your Fragment. Review the call site carefully, before you use requireContext(). I only use requireContext() when I'm certain that the fragment is going to be attached to the Activity at the time or the use case is so essential that it is better to crash with this assertion than any other course of action.
If for any reason the fragment could happen to be unattached and you can handle it yourself at the call site by avoiding or early returning, then the better idea is to null check the return from getContext() and only then proceed forward.
Typical Kotlin code for the null check looks like this:
fun myFragmentFunction(){
val context = getContext() ?: return // early return using Elvis operator
context.whatever() // guaranteed non-null context at this point
}
As Tam Huynh said, this crash happens when we our fragment is not attached. I had the same problem that your (but with a bottomSheet) and now it works fine.
We can receive this kind of error from two reasons:
requireContext() can crash directly if the context is null
Calling getString(R.string.xxx_xxx_xxx) from fragment will crash if the fragment is detached (because we will need the context and the context is null).
For me, with that piece of code we can check if our fragment is attached or not, and with that I solve the crash.
fun checkIfFragmentAttached(operation: Context.() -> Unit) {
if (isAdded && context != null) {
operation(requireContext())
}
}
More info => https://weidianhuang.medium.com/android-fragment-not-attached-to-a-context-24d00fac4f3d
In this scenario validate if you don't have any class level properties which are dependent on context as the fragment is not committed it won't have the context and we might end up with this exception.

How to start a FlutterActivity and change route

Im trying to make an alarm clock using flutter, and start the flutter activity with a path to the alarm ("/alarm" path). I have acccomplished starting the MainActivity using MethodChannels, but i need to somehow route to "/alarm", but calling getFlutterView().pushRoute("/alarm") does not do anything. The activity just starts in the main view instead of alarm route.
Thanks in advance!
I have managed to startActivity using MethodChannels, but when calling getFlutterView().pushRoute("/alarm") in onCreate, it does not change the route.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
// This does not change the route (setInitialRoute doesn't work either)
getFlutterView().pushRoute("/alarm");
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
#Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("setAlarm")) {
// pushRoute works here, but not in onCreate
getFlutterView().pushRoute("/alarm");
} else {
result.notImplemented();
}
}
}
);
}
Expected results: Change the route to "/alarm"
Actual results: Nothing happens, the activity gets opened on initial route eg. the main page
Found the solution,
pushRoute() or setInitialRoute() doesn't work until the View has been inflated.
Here is the code that works:
FlutterView.FirstFrameListener mListener = new FlutterView.FirstFrameListener() {
#Override
public void onFirstFrame() {
getFlutterView().pushRoute("/alarm");
}
};
getFlutterView().addFirstFrameListener(mListener);

Android AsyncTask RecyclerView does not populate

I want to make an app lister application which fetches the application list via the packagemanager through an AsyncTask<Void, Void, List<PackageSummary>> nested in a singleton class. However, if and only if I implement the async task, the RecyclerView will not populate on the first OnCreate.
I am sure I am doing a silly mistake and/or do not understand AsyncTask and RecyclerView well enough, but for the love of me I cannot find the root of the issue.
In my toy app repository I have prepared two, relatively cleaner branches for illustration purposes:
One in which the packages are fetched in the main thread, and the recyclerview populates on first Oncreate (git_UI_thread).
One in which an AsyncTask<Void, Void, List<PackageSummary>> class is called. The application persistence is not set yet (on purpose), and the RecyclerView will only populate after the application is rotated (git_background_thread).
For those who are not inclined to click on the bitbucket link above, the code snippet of the inside of my AsyncTask looks like this:
#Override
protected void onPostExecute(List<SingletonPackageSummarySupplier.PackageSummary> packageSummaries) {
super.onPostExecute(packageSummaries);
isQueryingInProgress = false;
packageSummaryList = packageSummaries;
}
#Override
protected List<SingletonPackageSummarySupplier.PackageSummary> doInBackground(Void... voids) {
List<PackageSummary> installedPackages = new ArrayList<>();
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
List<ResolveInfo> resolveInfoList = context.getPackageManager().queryIntentActivities(intent, 0);
for (ResolveInfo resolveInfo : resolveInfoList) {
ActivityInfo activityInfo = resolveInfo.activityInfo;
installedPackages.add(new PackageSummary(resolveInfo.activityInfo));
}
return installedPackages;
}
And this is my Main activity OnCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
psList = SingletonPackageSummarySupplier.getInstance(context).getPackageSummaryListReadOnly();
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerViewLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(recyclerViewLayoutManager);
adapter = new AdapterApplist(context, psList);
recyclerView.setAdapter(adapter);
}
And this is how the singleton is fetched:
static SingletonPackageSummarySupplier instance;
public static SingletonPackageSummarySupplier getInstance(Context context) {
if (instance == null) {
instance = new SingletonPackageSummarySupplier(context);
} else{
instance.updateInstance(context);
}
return instance;
}
P.S.: I think (but not sure) the singleton pattern is justified in order to diminish the changes of memory leaks.
P.S.2: I have read a couple questions about this, but none had an accepted / working solution.
There are two steps: Update your data to AdapterApplist and notify it. Hence you should create a new method like:
public void setData(List<SingletonPackageSummarySupplier.PackageSummary> list)
{ //reset your data ere
}
inside AdapterApplist class. Then, update your post:
#Override
protected void onPostExecute(List<SingletonPackageSummarySupplier.PackageSummary> packageSummaries) {
super.onPostExecute(packageSummaries);
isQueryingInProgress = false;
packageSummaryList = packageSummaries;
adapter.setData(packageSummaryList);
adapter.notifyDataSetChanged();
}
Just add adapter.notifyDataSetChanged(); in your postexecute method of AsyncTask
Either I was unable to implement the observer pattern (note that the asynctask was in a singleton class separate from the recyclerview and activity), or it is just not working well in this case.
Anyway, I ended up fixing the issue with the relatively new lifecycle-aware components: MutableLiveData, LiveData and AndroidViewModel (instead of viewmodel, which does not get context as constructor parameter). It is simple and elegant.
The key part was this, in the activity:
PackageSummarySupplier model = ViewModelProviders.of(this).get(PackageSummarySupplier.class);
model.getPackageSummaryList().observe(this, packageSummaryList -> {
adapter = new AdapterApplist(context, packageSummaryList);
recyclerView.setAdapter(adapter);
});

Refresh adapter in a Fragment after DialogFragment dbflow insert

I have an activity that has 3 fragments on it with Tabs, one of them is called "TaskFragment".
In my main Activity i only load the fragments.
In TaskFragment i have a RecyclerView that is working fine and is showing the items as intended.
The problem comes, when i insert data using a DialogFragment, because it does insert data (i am using DbFlow ORM), but it does not (of course) refresh the adapter since it is in the TaskFragment fragment inside the DetailMainActivity activity as i said.
I have tried to use onResume() and onPause() in order to refresh the adapter, but they are never called since the activity does not get paused or in onresume for a DialogFragment.
I have tried aswell to use an interface, but it does not work and i have searched all over stackoverflow and google with no luck.
I leave here some of my code for you to understand better:
DetailMainActivity.java
Here in the onClick interface i show the DialogFragment to the user to input the information.
FragmentManager fm = getSupportFragmentManager();
AddSimpleTask sptask = new AddSimpleTask();
sptask.show(fm, "tag");
TaskFragment.java
In this fragment i have my RecyclerView
private void setupRecyclerView() {
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
mRecyclerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (DetailMainActivity.FAB_Status) {
DetailMainActivity.hideFAB();
DetailMainActivity.FAB_Status = false;
}
return false;
}
});
}
private void setupAdapter() {
adapter = new DetailMainTaskAdapter(simpleTaskList, this);
}
AddSimpleTask
And this is my DialogFragment. I have set a setOnShowListener() in order to avoid the DialogFragment to get dismiss early.
#Override
public void onShow(DialogInterface dialogInterface) {
final AlertDialog dialog =(AlertDialog) getDialog();
if (dialog != null){
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mEditTextName.getText().toString().trim().isEmpty() ||
mEditTextContent.getText().toString().trim().isEmpty() ) {
if (mEditTextName.getText().toString().trim().isEmpty()) {
mEditTextName.setError("Can not be empty");
}
if (mEditTextContent.getText().toString().trim().isEmpty()) {
mEditTextContent.setError("Can not be empty");
}
}else {
presenter.beingInsertion(mEditTextName.getText().toString().trim(), mEditTextContent.getText().toString().trim()
, foreignId);
}
}
});
negativeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
}
});
}
}
If the insert is successfully achieved the onInsertSuccess method is called (i am using MVP)
#Override
public void onInsertSuccess() {
Snackbar.make(getActivity().findViewById(R.id.containerMainDetail), "Actividad agregada", Snackbar.LENGTH_SHORT).show();
dismiss();
}
I have called adapter.notifyDataSetChanged() in many places, and i also tried with a custom interface, but i can not make this work.
Sorry for the long post, but thanks in advance for your help.
There are some errors in your statement but I'll get to that later. notifyDataSetChanged() only notifies the adapter that the underlying list (or array) has changed. The implication is that you first need to requery your database and obtain the new list before calling notifyDataSetChanged() on the adapter else there is no point as the underlying list will still be the same and it will not update the adapter.
The correct way of calling this will be through your custom listener interface and not in the onPause()/onResume() callbacks as there is the possibility that the user does not enter a value and hence you will unnecessarily be querying the database. In your custom listener interface implementation, first update the list with the new data from the DB and then notify the adapter.
Which leads to the error in assumption that onPause()/onResume() callbacks do not happen when your Activity is covered by a DialogFragment - this is incorrect. The moment the activity view is even partially covered, the onPause() callback is triggered.

Activity returning null and producing NullPointerException even though I implemented it

I have a class extending ParseQUeryAdapter so I can use the notifyDataSetChanged feature. The adapter class is called mainAdapter.
Here's my notifyDataSetCHanged method in the mainAdapter:
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
MainActivity mainActivity = new MainActivity();
mainActivity.getItems();
}
Here's my getItems() method in MainActivity:
public void getItems(){
if(adapter == null){
Toast.makeText(MainActivity.this, "null", Toast.LENGTH_SHORT).show();
}
}
The app crashes on loading. As you can see, I planted an if so that I can see if adapter was null. But it still crashes.
According to the debugger, it says in green after getting to the if line, "adapter:null". However, I have this in onCreate():
adapter = new mainAdapter(this);
And I declared it:
mainAdapter adapter
Is there a method I can put in that will solve my issue? Although I am implementing the class, why is it still null? I clearly stated that adapter = new mainAdapter()
You should never instantiate your activity classes with new; they should be interacted with using startActivity() and related APIs.
An activity created with new won't be registered with the ActivityManager, won't show up on the screen, and won't have any of it's lifecycle callbacks called.
Since your MainActivity instance's onCreate() method has not been run, adapter has not been created.
In your case, it seems like you would want your ParseQueryAdaptor subclass to have a reference to your activity in some way, so that it can access the right one.
If you want to reference an existing activity then you have to pass the activity object to where you want to use it (or use getContext or getActivity when your in a class that has that available).
One of the things you can do is create a method that passes the MainActivity object into your ParseQUeryAdapter. Then when you are calling stuff in your adapter do: activityObject.whatevermethodyouwantocallontheactiviy() be shure to error check the activity object first though.
IE:
private MainActivity activity;
public void setup(MainActivity activity){ this.activity = activity;}
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
if(activity != null){
activity.getItems();
}
}

Categories

Resources