I have of null object inside a fragment. The basic idea is that I have an activity that fetches a database asynchronously. However my recyclerview where I will populate the data lives into a fragment. The pseudo-code is more or less
ACTIVITY:
public void onCreate(Bundle savedInstanceState) {
//kicks off a query to the server
mData = new Gson().fromJson(getIntent().getExtras().getString(Constants.MYDATA), MyData.class);
if (mVenue == null) {
finish();
return;
}
// a bunch of stuff
// create a fragment
mMyFrag = new MyFrag();
}
public void CallBackWhenDone(final List<DataSet> dataset) {
// notify the frag that we are done
mMyFrag.notifyDataSetChanged(messages);
}
FRAGMENT:
private RecyclerView mRV;
private ParentActivity mActivity;
private ActivityAsynchData mAsynchData;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recycler, container, false);
mRV = (RecyclerView) view.findViewById(R.id.list);
mRV.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
if (null != mActivity) {
mAsynchData = mActivity.GetAsynchData();
}
if (null != mAsynchData) {
mRV.setAdapter(new MyRecyclerAdapter(getActivity(), mAsynchData));
}
}
// mRV is null when the activity "CallBackWhenDone" calls the frag
// all private variables are gone! why?
public void notifyDataSetChanged(final List<Message> messages) {
MyRecyclerAdapter adapter = ((MyRecyclerAdapter) mRV.getAdapter());
adapter.setMessageList(messages);
adapter.notifyDataSetChanged();
}
I ended up hacking my recycler (mRV in this case) view to be static, but this looks super hacked.
any way around? In other words how can I fetch my "mRV" after the fragment has been created and the private vars are all gone.
What i could understand is, that you initialised the fragment and try to access the recycler view in that but its throwing you null. I am not surprised to see it being as null. The way you have called the method is not correct.You need to get the hosted and already running fragment, instance try doing this:
if you are using support fragment use getSupportFragmentManager instead of getFragmentManager.
MyFrag fragment = (MyFrag) getFragmentManager().findFragmentById(R.id.fragmentHolder);
fragment.<specific_function_name>();
Here , the R.id.fragmentHolder is the id of the frame layout or any layout that you are using to host your fragment inside the an activity.
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragmentHolder"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
Related
Im working on loading data from Firebase Realtime Database by ViewModel. In my layout I have progress bar and recyclerview. Things work perfecly when i need to load my data first, when I click on button fragment opens instantly, progress bar is running and when data loads it stop running and recyclerview shows up. But, when i go into that fragment again (Data is already loaded), no progress bar is shown (which is okay), but it takes about second to comming that switch, which is significantly slower than first behaviour that i described.
So, I am wondering what is making it to wait that long in second scenario and how can I override it, so my fragment shows up first and then shows up recyclerview?
I have already tried using viewstub and dummy views but nothing seems to work..
My CategoryFragment
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
homeViewModel = new ViewModelProvider(requireActivity()).get(MenuViewModel.class);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(root == null || fragmentState == STATE_STARTUP)
{
binding = FragmentCategoryRecyclerBinding.inflate(inflater, container, false);
root = binding.getRoot();
binding.categoryRecycler.setHasFixedSize(true);
binding.categoryRecycler.setLayoutManager(new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false));
layoutAnimationController = AnimationUtils.loadLayoutAnimation(getContext(), R.anim.layout_item_fade_scale);
myFoodListAdapter = new MyFoodListAdapter(getContext(), foodModelList, String.valueOf(menuIndex), String.valueOf(categoryIndex));
binding.categoryRecycler.setAdapter(myFoodListAdapter);
}
else
binding = FragmentCategoryRecyclerBinding.bind(root);
return root;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
homeViewModel.getMessageError().observe(getViewLifecycleOwner(), s -> Toast.makeText(getContext(), "" + s, Toast.LENGTH_SHORT).show());
homeViewModel.getMenuList(restaurantId).observe(getViewLifecycleOwner(), menuModels -> {
if(fragmentState == STATE_STARTUP || fragmentState == STATE_SEARCHING)
{
binding.categoryRecycler.setLayoutAnimation(layoutAnimationController);
myFoodListAdapter.setFoodModelList(menuModels.get(menuIndex).getCategories().get(categoryIndex).getItems());
binding.progressBar.hide();
binding.categoryRecycler.setVisibility(View.VISIBLE);
fragmentState = STATE_INITIALIZED;
if(foodModelList.isEmpty())
foodModelList = menuModels.get(menuIndex).getCategories().get(categoryIndex).getItems();
myFoodListAdapter.notifyDataSetChanged();
}
});
}
#Override
public void onDestroyView() {
binding = null;
super.onDestroyView();
}
Sorry for the late reply
Use viewmodel or androidviewmodel in onstart instead on create
I was using a recyclerview in an activity and now decided to use it in a fragment instead. Now my code crashes at layoutmanager = new LinearLayoutManager (globalContext); with a NullPointerException.
I have no idea why. As I said it worked perfectly fine when used in activity.
I'm using Xamarin but that really doesn't make any difference.
I have only included the first parts of the code as app crashes before reaching others.
private Context globalContext = null;
RecyclerView recyclerview;
RecyclerView.LayoutManager layoutmanager;
NewsAdapter adapter;
public LatestNewsFragment()
{
this.RetainInstance = true;
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.fragment_latestnews, null);
// Get our RecyclerView layout:
recyclerview = view.FindViewById<RecyclerView> (Resource.Id.recyclerView);
return view;
}
public override async void OnActivityCreated(Bundle savedInstanceState)
{
base.OnActivityCreated(savedInstanceState);
globalContext = this.Context;
//............................................................
// Layout Manager Setup:
// Use the built-in linear layout manager:
layoutmanager = new LinearLayoutManager (globalContext);
// Plug the layout manager into the RecyclerView:
recyclerview.SetLayoutManager (layoutmanager);
//............................................................
// Adapter Setup:
// Create an adapter for the RecyclerView, and pass it the
// data set to manage:
var rest = new RestAccess();
var listofarticles = await rest.ListArticlesAsync("somesource", "1");
adapter = new NewsAdapter (listofarticles, globalContext);
// Register the item click handler (below) with the adapter:
adapter.ItemClick += OnItemClick;
// Plug the adapter into the RecyclerView:
recyclerview.SetAdapter (adapter);
}
// Handler for the item click event:
void OnItemClick (object sender, int position)
{
// Display a toast that briefly shows the enumeration of the selected photo:
int photoNum = position + 1;
Toast.MakeText(globalContext, "This is card number " + photoNum, ToastLength.Short).Show();
}
}
Edit:
So I changed the OnCreateView to :
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.fragment_latestnews, null);
// Get our RecyclerView layout:
recyclerview = view.FindViewById<RecyclerView> (Resource.Id.recyclerView);
// Plug the layout manager into the RecyclerView:
recyclerview.SetLayoutManager (new LinearLayoutManager (Activity));
return view;
}
And still getting nullpointer exception at recyclerview.SetLayoutManager (new LinearLayoutManager (Activity));
Edit 2:
variable recyclerview is actually null. Don't know why.
Answer:
My app was crashing for two reasons:
1- layoutmanager must be defined in OnCreateView but I was doing it in OnActivityCreated.
2- My fragment layout file didn't include the RecyclerView.
You need to set LayoutManager in onCreateView() after initializing RecyclerView, instead of setting in onActivityCreated():
mRecyclerView = (RecyclerView)v.findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(newLinearLayoutManager(mRecyclerView.getContext()));
Instead of this.Context use getActivity() to get Context in Fragment class. Change:
globalContext = this.Context;
to
globalContext = getActivity();
Try with getActivity()
layoutmanager = new LinearLayoutManager (getActivity());
Im using 3 fragments in my ViewPager adapter. I will be loading data from Parse(parse.com) and displaying them in recycler views. The following code is causing my app to crash. What my understanding is that when my MainActivity loads, this is my first fragment ie, it gets viewed by user immediately so the setUserVisibleHint function gets called immediately and in that v.findViewById code causes a null pointer exception since setContentView hasnt/ may not been called. My proof for this is if i add a 1sec delay to setUserVisisbleHint then my code works properly.
Now I want to add server PULL requests using Parse, add data in a list to an adapter and populate recyclerview AFTER user views the page so
1) Should i add all the code in setUserVisisbleHint and just add a 0.5secc delay so it gets executed after setContentView is called ensuring I dont get a null pointer exception error OR
2) Is there a better way/ other functions I can use to achieve the same?
public class NewsFeed extends Fragment {
LinearLayoutManager mLayoutManager;
boolean _areLecturesLoaded=false;
View v;
ProgressBar bar;
public NewsFeed() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
// Inflate the layout for this fragment
v=inflater.inflate(R.layout.fragment_news_feed, container, false);
RecyclerView mRecyclerView = (RecyclerView)v.findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(v.getContext());
mRecyclerView.setLayoutManager(mLayoutManager);
MyStickyAdapter mAdapter = new MyStickyAdapter(v.getContext());
mRecyclerView.setAdapter(mAdapter);
// mRecyclerView.addItemDecoration(new StickyRecyclerHeadersDecoration(mAdapter));
return v;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && !_areLecturesLoaded ) {
_areLecturesLoaded = true;
v.findViewById(R.id.asd).setVisibility(View.GONE);
}
}
}//Closes Fragment
Im using this library for my recycler view StickyHeaderRecyclerView
I would highly recommend avoiding adding delays especially if you're running this on the main thread. To avoid the NPE, try moving the findViewById(R.id.asd) into your onCreateView method after you inflate the view:
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
// Inflate the layout for this fragment
v=inflater.inflate(R.layout.fragment_news_feed, container, false);
v.findViewById(R.id.asd).setVisibility(View.GONE);
This is assuming that R.id.asd is in R.layout.fragment_news_feed
This post could also provide some insight:
setUserVisibleHint called before onCreateView in Fragment
I have two fragments on a view pager.
I once had to move data from fragment B to A and refresh the data displayed on A and I did it with getItemPosition.
For some reason, the same method doesn't work when I try to reset all data..
In my adapter i have :
public void refresh()
{
notifyDataSetChanged();
}
#Override
public int getItemPosition( Object obj )
{
return POSITION_NONE;
}
in fragment where I click 'reset' :
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
notTriedPasswordsList = PagerActivity.mainList;
.....
....
resetButton.setOnClickListener( new View.OnClickListener() {
#Override
public void onClick( View v )
{
PagerActivity.resetPasswords();
PagerActivity.viewPagerAdapter.refresh();
}});
viewPager activity hosting both fragments:
public static void resetPasswords()
{
mainList.addAll( 0, historyList );
historyList.clear();
PagerActivity.viewPagerAdapter.refresh();
}
Main fragment where the pass is displayed :
#Override
public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
{
View view = inflater.inflate(R.layout.fragment_main, container, false);
.....
nextCodeDisplay = ( TextView ) view.findViewById( R.id.passwordDisplayTextView );
nextCodeDisplay.setText( notTriedPasswordsList.get( 0 ).getPasswordString() );
....
nextButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v)
{
notTriedPasswordsList.remove( 0 );
if( notTriedPasswordsList.size() > 0 && !(notTriedPasswordsList.get( 0 ).getTried()) )
{
nextCodeDisplay.setText( notTriedPasswordsList.get( 0 ).getPasswordString() );
}
}
PagerActivity is treated like a static class, and you can only access static methods and member data and objects in this way. About code:
notTriedPasswordsList = PagerActivity.mainList;
Note: So now PagerActivity can access static mainList object, or notTriedPasswordsList (sharing the same memory). But this is the only object you can access since your code references static methods.
On code PagerActivity.viewPagerAdapter.refresh(), I am not clear on what data this refreshes since I don't see the enough code, again refresh() must be a static method. With code notifyDataSetChanged(), there must be a direct link between viewPagerAdapter and the data object, probably an ArrayList. Certainly I don't see any direct relation between the two.
Perhaps you want code like:
viewPagerAdapter pagerAdapter = new viewPagerAdapter();
This way you can have the relationship between the adapter and possibly an ArrayList object. The benefit of creating an instance with new is that it saves data and the state inside the class in the form of an object, in my sample that is pagerAdapter.
I could not suggest specific set of codes for now since I don't see sufficient amount of it for me to fix. Perhaps you can fix code first and then we all can contribute.
Your call to PagerActivity.viewPagerAdapter.refresh(); won't cause your fragment to be redrawn. Instead you should access your fragment directly and create a custom refreshUI() method in it.
public void refreshUI(){
nextCodeDisplay.setText( notTriedPasswordsList.get( 0 ).getPasswordString() );
}
I suggest to change your approach. I've uploaded a simple project to my dropbox public folder. Here you can find a reference implementation of how two fragments managed by a ViewPager can share information. The first Fragment - Fragment#1 - simply displays a String that is generated by Fragment#2. Fragment#2 has a button that, when clicked, sends a random String to Fragment#1 through the Activity. No need to refresh viewpager, no need of static methods, simple and working. I guess you can adapt this example to your needs.
As you said that you want to refresh your data, personally i would like to suggest to use swipe refresh layout. It will be very useful for this purpose and stylish as well. Following is the code.
Swipe_Refresh_layout.xml
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView android:id="#+id/listview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
/>
</android.support.v4.widget.SwipeRefreshLayout>
And following is the activity i am using for this layout.
public class LatestNewsFragment extends Fragment implements OnRefreshListener ,OnScrollListener{
SwipeRefreshLayout swipeLayout;
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
{
=
View rootView = inflater.inflate(R.layout.Swipe_Refresh_layout, container, false);
swipeLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_container);
swipeLayout.setOnRefreshListener(this);
swipeLayout.setColorScheme(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
return rootView;
}
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
#Override public void run() {
swipeLayout.setRefreshing(false);
additemstatus();
}
}, 5000);
}
Now in overridden refresh method you can refresh or load your data. I hope this will be very helpful for your support.
I have an activity and inside it a fragment, where i'm trying to display a gridview with pictures.
If I try to access the view in onCreateView() of the fragment, i do get a non-null handler and all is OK :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.bar_pics_grid, container, false);
GridView gridview = (GridView) v.findViewById(R.id.gridview);
return v;
However, since i need to get data from a DB first, so I access the gridview in a custom function :
public void loadData(Bar bar) {
if (showBarDetails.getBar() != null) {
bar = showBarDetails.getBar();
GridView gridview = (GridView) getView().findViewById(R.id.gridview);
barImageArray = bar.getImages().toArray(new BarImage[bar.getImages().size()]);
gridview.setAdapter(new PicsAdapter(showBarDetails, barImageArray));
}
}
In the latter case, gridview is always null, which means this function is called before onCreateView - any ideas why is this happening ?
I initialize the Fragment in the activity's onCreate function as follows :
picsFragment = new PicsFragm(this);
Then I have an AsyncTask pulling data from the DB where I call :
#Override
protected void onPostExecute(AsyncTaskResult<Bar> result) {
if (result.getError() == null) {
sbd.setBar(result.getResult());
if (sbd.getPicsFragment().isResumed())
sbd.getPicsFragment().loadData(sbd.getBar());
}
}
Many thanks
Since your custom function is called before the onCreateView, I would suggest initializing a global variable(s) in your custom function then calling the variable(s) in your onCreateView method.
//global variable declaration like a string or data source e.g
String myValue;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.bar_pics_grid, container, false);
//Get your global variable here
String getValue = myValue;
GridView gridview = (GridView) v.findViewById(R.id.gridview);
return v;
}
public void loadData(String bar) {
//initialize your global variable here
myValue = bar;
}
The Android documentation suggests that to communicate from an activity to a hosted fragment, the fragment can define a callback interface and require that the host activity implement it. The basic pattern involves implementing onAttach in your fragment, and casting the activity to a callback inteface.
See http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity
I'd use a ReentrantLock to fix this. Create a variable in your fragment activity to hold the Database data:
private ReentrantLock dataLocker = new ReentrantLock();
private PicsAdapter adapter = null;
In loadData(Bar bar) rather than loading it directly into the grid view, load it into that variable:
if (showBarDetails.getBar() != null)
{
bar = showBarDetails.getBar();
barImageArray = bar.getImages().toArray(new BarImage[bar.getImages().size()]);
dataLocker.lock();
try
{
adapter = new PicsAdapter(showBarDetails, barImageArray);
//Check if the grid view is made (this is run after inflation)
GridView gridview = (GridView) getView().findViewById(R.id.gridview);
if (gridview != null)
{
gridview.setAdapter(adapter);
}
}
finally
{
dataLocker.unlock();
}
}
Then when you inflate your view, you make lock the dataLocker whilst you inflate. Once inflated, set the adapter if it already exists.
dataLocker.lock();
try
{
View v = inflater.inflate(R.layout.bar_pics_grid, container, false);
GridView gridview = (GridView) v.findViewById(R.id.gridview);
if (adapter != null)
gridview.setAdapter(adapter);
return v;
}
finally
{
dataLocker.unlock();
}
I'm not sure what thread you called execute on the AsyncTask on, so we use the ReentrantLock to make sure the threads aren't interfering with each other.