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.
I save the web-view state in fragment with ViewModel in java. As this way:
public class PageViewModel extends ViewModel {
private MutableLiveData<CustomWebView> liveData = new MutableLiveData<>();
public void setWebView(CustomWebView webView) {
liveData.setValue(webView);
}
public LiveData<CustomWebView> getWebView() {
return liveData;
}
}
In onCreate in Fragment and save current web-view state:
pageViewModel = new ViewModelProvider(this).get(PageViewModel.class);
pageViewModel.setWebView((CustomWebView) view);
And finally in onConfigurationChanged in Fragment:
#Override
public void onConfigurationChanged(#NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
pageViewModel.getWebView().observe(requireActivity(), new Observer<CustomWebView>() {
#Override
public void onChanged(CustomWebView customWebView) {
webView = customWebView;
}
});
}
So, When orientation is change, the web-view not reloading. But when I use this way in kotlin, The Webview not save state and it reloaded.
PageViewModel:
class PageViewModel : ViewModel() {
val liveData = MutableLiveData<CustomWebView>()
fun setWebView(webView: CustomWebView?) {
liveData.value = webView
}
fun getWebView(): LiveData<CustomWebView?>? {
return liveData
}
}
In global variables: private lateinit var pageViewModel: PageViewModel and in onCreate method of fragment:
pageViewModel = ViewModelProvider(activity!!).get(PageViewModel::class.java)
pageViewModel.setWebView(view as CustomWebView)
And finally in onConfigurationChanged in Fragment:
pageViewModel.getWebView()?.observe(requireActivity(), Observer { customWebView ->
if (customWebView != null) {
webView = customWebView
}
})
Can you help me? Where did I go wrong?
Your way is fine and it works well. I tested your code but in my app, onConfigurationChanged not ran. I searched for this problem and add this line to the activity tag in the manifest:
android:configChanges="orientation|screenSize"
And then * onConfigurationChanged * was run.
I am using a Handler to display a timer in RecyclerView list item. When I press back the Activity that hosts the RecyclerView is completely destroyed, the Handler() still running in the background. The handler is created and initiated in ViewHolder. Is there any way to remove the callbacks from handler from ViewHolder itself?
My ViewHolder sample code
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), CustomRunnable.CustomRunnableListener{
private val handler = Handler()
lateinit var customRunnable: CustomRunnable //custom runnable for my timer logic
fun bind(position: Int, listModelClass: ModelClass?){
if(someCondition){
customRunnable = CustomRunnable(handler, this, textView, listModelClass)
handler.postDelayed(customRunnable, 1000)
}
}
override fun onTimerFinish(listModelClass: ModelClass) {
// I get this call back when the timer finishes
handler.removeCallbacks(customRunnable)
}
}
As per my knowledge, there is no method on adapter that is called when RecyclerView is detached from activity.
Try creating a timer object or a list of objects in your BaseActivity or Application Class and after pressing onBack run a method that will stop that timer or timers.
//Declare timer
CountDownTimer cTimer = null;
//start timer function
void startTimer() {
cTimer = new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
}
};
cTimer.start();
}
//cancel timer
void cancelTimer() {
if(cTimer!=null)
cTimer.cancel();
}
You can do it onDestory() of activity
handler.removeCallbacksAndMessages(null);
Remove any pending posts of callbacks and sent messages whose obj is token. If token is null, all callbacks and messages will be removed.
if your Activity extended from androidx.activity.ComponentActivity, you can do this easily, just bind the lifecycle event then the super class would done their job as your desired, the sample code like below:
internal class SampleViewHolder(
private val activity: TheActivity,
view: View
) : RecyclerView.ViewHolder(view) {
fun bind(item: SampleInfo, position: Int) {
val view = itemView
...
bindLifecycle()
}
fun onViewRecycled() {
itemView.imv_sample.onViewRecycled(sampleAdapter.playStateStore)
activity.lifecycle.removeObserver(lifecycleObserver)
}
private val lifecycleObserver = object : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private fun onDestroy() {
MLog.info(TAG, "receive destroy event")
itemView.imv_sample.onHostActivityDestroyed()
}
}
private fun bindLifecycle() {
activity.lifecycle.run {
MLog.info(TAG, "success bind lifecycle")
addObserver(lifecycleObserver)
}
}
}
When the activity is destroyed, set the list adapter to null. This will make sure onViewDetachedFromWindow is called for all the views in list when the activity is destroyed.
#Override
public void onDestroy() {
mList.setAdapter(null);
super.onDestroy();
}
And then you can remove the callbacks from the handler running inside the viewHolder. This requires you to save the handler reference inside your viewHolder.
#Override
public void onViewDetachedFromWindow(#NonNull PurchaseItemViewHolder holder) {
if (holder.handler != null) {
holder.handler.removeCallbacksAndMessages(null);
}
super.onViewDetachedFromWindow(holder);
}
This is a part of my activity class,
public class StatusActivity extends AppCompatActivity {
private boolean cFlag = false;
public boolean getFlag() { return cFlag; }
public void setFlag(boolean cFlag) {
this.cFlag = cFlag;
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
android.R.id.text1, messages);
ListView listView = findViewById(android.R.id.list);
listView.setAdapter(adapter);
adapters.add(adapter);
Button btn = findViewById(R.id.btnCustomerCheckIn);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setFlag(true);
cFlag = getFlag();
Intent intent = new Intent(StatusActivity.this, MainActivity.class);
Toast.makeText(StatusActivity.this, "customer checked in",
Toast.LENGTH_LONG).show();
startActivity(intent);
}
});
}
this is a part of another class named as position
public class Position {
StatusActivity statusactivity = new StatusActivity();
public boolean ccflag = statusactivity.getFlag();
statusactivity.setFlag(false);
}
when i am calling
statusactivity.setFlag(false);
it is showing an error. couldn't recognize that what is the error that i am getting. but
statusactivity.getFlag();
is working properly. any help is appreciated
StatusActivity statusactivity = new StatusActivity();
This is totally wrong because you are trying to create a new Instance of activity.
If you want to use "setFlag" method from other activity then you must create a static method inside StatusActivity so you can access using directly StatusActivity.
And If you want to call from any fragment of this activity then please get an instance of this activity by the cast from "getActivity()" to StatusActivity and use that instance for call "setFlag" or "getFlag" method.
You can implement like below in Activity.
private static boolean cFlag = false;
public static boolean getFlag() {
return cFlag;
}
public static void setFlag(boolean cFlag) {
StatusActivity.cFlag = cFlag;
}
and call from position class like below
public class Position {
public boolean ccflag = StatusActivity.getFlag();
StatusActivity.setFlag(false);
}
you can not instantiate Activity class. if you want to call a method from activity, fist you should check that activity already running and not destroyed then by having the context of your class you just cast it like below then use its method
StatusActivity statusactivity= (StatusActivity )context;
statusactivity.setFlag(false);
So first things first, here's the error that I'm getting:
java.lang.IllegalStateException: The application's PagerAdapter
changed the adapter's contents without calling
PagerAdapter#notifyDataSetChanged! Expected adapter item count: 18,
found: 28
I'm using a RecyclerView in my main activity and that has a List<Objects> as the dataset, that's all fine.
From that activity I call the second activity when a RecyclerView item is clicked that is basically a gallery implemented using a ViewPager using this code:
public void startSlideActivity(final int position) {
DataTransferer.get().storeItems(feed);
Intent i = new Intent(context, SlideActivity.class);
...
i.putExtra("POSITION", position);
context.startActivity(i);
}
My data is too large to transfer through an intent (using Parcelable or otherwise) so I'm using a singleton to hold and transfer my list, here's the code:
public class DataTransferer {
private static volatile DataTransferer singleton;
private List<Thing> items;
public static DataTransferer get(){
if (singleton == null) {
synchronized (DataTransferer.class) {
singleton = new DataTransferer();
}
}
return singleton;
}
private DataTransferer(){
}
public void storeItems(List<Thing> items){
this.items = items;
}
public List<Thing> getStoredItems(){
return items;
}
}
In the second activity I set the adapter and retrieve the list like so:
feed = DataTransferer.get().getStoredItems();
final int position = this.getIntent().getIntExtra("POSITION", 0);
adapter = new FeedPagerAdapter(SlideActivity.this, feed);
viewPager.setAdapter(adapter);
adapter.notifyDataSetChanged();
viewPager.setCurrentItem(position);
viewPager.setOffscreenPageLimit(4);
And finally here's in my PagerAdapter code:
public class FeedPagerAdapter extends PagerAdapter {
#BindView(R.id.item_image_view) ImageView image;
private final SlideActivity host;
private List<Thing> items;
public FeedPagerAdapter(SlideActivity host, List<Thing> items){
this.host = host;
this.items = items;
notifyDataSetChanged();
}
#Override
public Object instantiateItem(ViewGroup parent, int position) {
View view = LayoutInflater.from(host).inflate(R.layout.item, parent, false);
ButterKnife.bind(this, view);
...
parent.addView(view);
return view;
}
#Override
public int getCount() {
return items.size();
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
I've tried notifying the dataset in onResume and onPause and getItemCount in the adapter also, same problem.
Back to the main activity, this data is loaded over the network and adds items to the list when the load finishes. If I start my application and click on the item as soon they start to populate the RecyclerView, it opens the second activity and I get the crash. If I start the app and wait a second and click a RecyclerView item, it works as intended.
If anyone has any suggestions on how I can wait to make sure the list is stable when starting the second activity or a better way to implement a grid based RecyclerView gallery to open a viewpager type layout with the same dataset I would really appreciate it.
It's because you are using this line viewPager.setOffscreenPageLimit(4);, It's mean viewpager won't re-create the screen in all 4 pages. However, there is a function to detect if your screen has been visible completely, it's call setUserVisibleHint(). You just need to use like below:
//For the Fragment case
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (isVisibleToUser) {
//TODO notify your recyclerview data over here
}
}
EDIT:
For the Activity case: If targeting API level 14 or above, one can use
android.app.Application.ActivityLifecycleCallbacks
public class MyApplication extends Application implements ActivityLifecycleCallbacks {
private static boolean isInterestingActivityVisible;
#Override
public void onCreate() {
super.onCreate();
// Register to be notified of activity state changes
registerActivityLifecycleCallbacks(this);
....
}
public boolean isInterestingActivityVisible() {
return isInterestingActivityVisible;
}
#Override
public void onActivityResumed(Activity activity) {
if (activity instanceof MyInterestingActivity) {
isInterestingActivityVisible = true;
}
}
#Override
public void onActivityStopped(Activity activity) {
if (activity instanceof MyInterestingActivity) {
isInterestingActivityVisible = false;
}
}
// Other state change callback stubs
....
}
Register your application class in AndroidManifest.xml:
<application
android:name="your.app.package.MyApplication"
android:icon="#drawable/icon"
android:label="#string/app_name" >
Add onPause and onResume to every Activity in the project:
#Override
protected void onResume() {
super.onResume();
MyApplication.activityResumed();
}
#Override
protected void onPause() {
super.onPause();
MyApplication.activityPaused();
}
In your finish() method, you want to use isActivityVisible() to check if the activity is visible or not. There you can also check if the user has selected an option or not. Continue when both conditions are met.