I have an inner AsyncTask which i have call from a fragment outside the fragment which contains this AsyncTask. I have read and saw some examples where they use an interface. I can't figure it out on how to implement it in my project.
This is my inner AsyncTask:
public class LoadQueueTask extends AsyncTask<Void, Void, Queue>
{
#Override
protected Queue doInBackground(Void... arg0) {
Model model = Model.getInstance();
return model.getQueue();
}
#Override
protected void onPostExecute(Queue result) {
super.onPostExecute(result);
queue = result;
if(result == null) {
listview.setEmptyView(empty);
progress.setVisibility(View.GONE);
listview.setVisibility(View.VISIBLE);
emptyText.setText("Empty Queue");
emptyImage.setImageResource(R.drawable.ic_action_warning);
} else {
if(result.getSlots().size() != 0) {
Handler mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
callAsynchronousTask();
}
}, 5000);
} else {
listview.setEmptyView(empty);
progress.setVisibility(View.GONE);
listview.setVisibility(View.VISIBLE);
emptyText.setText("No items found");
}
}
}
}
Here is my fragment where i want to execute this task:
public class Fragment extends SherlockFragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment, container, false);
}
ViewPager mViewPager = (ViewPager) rootView.findViewById(R.id.viewPager);
kbps = (TextView) rootView.findViewById(R.id.speed);
refresh = (ImageView) rootView.findViewById(R.id.refresh);
refresh.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//CALL ASYNCTASK HERE
}
});
return rootView;
}
You can use a decoupled messaging system as EventBus or Otto.
First fragment will be publisher and second subscriber. In the latter you'll start the AsyncTask.
Later on you can use the system anywhere in the app whenever you need to send an object from one component to another.
Simply move the AsyncTask into its own public class and you can call it from wherever you like. Have a callback interface and implement that in the fragments where you are calling the AsyncTask.
Create an interface for the Fragment class
public interface OnFragmentButtonListener{
onMyButtonClicked();
}
Now, have your activity hosting these fragments implement this interface.
In your OnClick method, have that call
((OnFragmentButtonListener)getActivity()).onMyButtonClicked();
Next create a method inside your Fragment Class hosting the AsyncTask inner class.
public void startAsyncTask(){
new LoadQueueTask.execute();
}
Inside the activity, you are forced to implement your interface method onMyButtonClicked();
In this method, get a handle to your fragment and call the startAsyncTask method in the fragment.
Related
I have a HostActivity that uses ViewPager and TabLayout to switch between multiple Fragments. When I switch between the tabs, the Fragments instance does get the updated data. I also see the updated data in onCreateView of the Fragment instance, but the TextView.setText does not get updated. When I check the visibility of Fragment, it always shows Invisible. How do I make the fragment visible when I switch tabs so that the view gets updated with new data? Is there something missing in the Fragment/Activity Lifecycle? I am implementing ViewPager for the first time so it will be helpful to know if I am missing something.
Fragment Class:
public class StepFragment extends Fragment { #Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (getArguments() != null) {
step = getArguments().getParcelable(SELECTED_STEP);
mDescription = step.getDescription();
}
View view = inflater.inflate(R.layout.step_fragment, container, false);
ButterKnife.bind(this,view);
Log.e(TAG, "onCreateView: "+mDescription); **// THIS GETS UPDATED DATA**
tvStepDescription.setText(mDescription);
}
return view;
}
}
Here is my Host Activity:
public class StepActivity extends AppCompatActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_step);
fragmentSelectAdapter = new StepFragmentSelectAdapter(getSupportFragmentManager(),this,steps,recipe);
mViewPager.setAdapter(fragmentSelectAdapter);
mTabLayout.setupWithViewPager(mViewPager);
stepFragment = (StepFragment) getSupportFragmentManager().findFragmentById(R.id.step_container);
if(stepFragment == null) {
stepFragment = StepFragment.newInstance(step, recipe);
stepFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.add(R.id.step_container, stepFragment)
.commit();
} else {
stepFragment = StepFragment.newInstance(step, recipe);
stepFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.step_container, stepFragment)
.commit();
}
}
}
Here is my FragmentPagerAdapter, which seems to be getting the correct data as per the tab position in getItem method:
public class StepFragmentSelectAdapter extends FragmentPagerAdapter {
...
#Override
public Fragment getItem(int position) {
**// THIS GETS UPDATED DATA**
Log.e(TAG, "getItem: \nDecr: "+steps.get(position).getDescription()+"\nVideo: "+steps.get(position).getVideoURL()+"\nImage: "+steps.get(position).getThumbnailURL());
return StepFragment.newInstance(steps.get(position),recipe);
}
#Override
public int getCount() {
if (steps == null){
return 0;
}
return steps.size();
}
...
}
As far as I could understand about the problem that you are having there, I think you should implement an onResume function in your StepFragment which will get the updated data from some source and will display this in the TextView. However, I can think of a potential problem in your StepFragmentSelectAdapter. You are creating a new instance each time you are switching the tabs.
You should have the Fragment instances created before and if you are about to pass the data among fragments, you might consider having a BroadcasReceiver or listener function by implementing an interface.
So the PagerAdapter should look something like this.
public class StepFragmentSelectAdapter extends FragmentPagerAdapter {
public ArrayList<StepFragment> stepFragments;
public StepFragmentSelectAdapter(ArrayList<Step> steps) {
stepFragments = new ArrayList<>();
for(int i = 0; i < steps.size(); i++) {
stepFragments.add(StepFragment.newInstance(steps.get(position),recipe));
}
}
...
#Override
public Fragment getItem(int position) {
**// THIS GETS UPDATED DATA**
Log.e(TAG, "getItem: \nDecr: "+steps.get(position).getDescription()+"\nVideo: "+steps.get(position).getVideoURL()+"\nImage: "+steps.get(position).getThumbnailURL());
return stepFragments.get(position);
}
#Override
public int getCount() {
if (steps == null){
return 0;
}
return steps.size();
}
...
}
Thanks for the hint. I got around this problem by replacing ActionBar with a custom ToolBar with back ImageButton and using click listener to get back to the calling activity.
backButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getActivity().onBackPressed();
}
});
I occasionally get NullPointerException when entering fragment. It happens when the app was in the background for a long time and then I open it and swipe to this fragment.
public class SummaryFragment extends Fragment implements FragmentLifecycle {
private static final String TAG = "DTAG";
private DateFormat dateFormatName;
private Preference prefs;
private List<String> monthList;
private TextView totalTimeFullTv;
private TextView totalTimeNetTv;
private TextView averageTimeTv;
private TextView overUnderTv;
private TextView minTimeTv;
private TextView maxTimeTv;
private TextView vacationsTv;
private TextView sickTv;
private TextView headlineTv;
private TextView overUnderTvH;
private OnFragmentInteractionListener mListener;
public SummaryFragment() {
// Required empty public constructor
}
public static SummaryFragment newInstance(String param1, String param2) {
SummaryFragment fragment = new SummaryFragment();
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View RootView = inflater.inflate(R.layout.fragment_summary, container, false);
dateFormatName = new SimpleDateFormat(getResources().getString(R.string.month_text));
monthList = Arrays.asList(new DateFormatSymbols().getMonths());
prefs = new Preference(GeneralAdapter.getContext());
totalTimeFullTv = RootView.findViewById(R.id.textView_sum_ttf);
totalTimeNetTv = RootView.findViewById(R.id.textView_sum_ttn);
averageTimeTv = RootView.findViewById(R.id.textView_sum_av);
overUnderTv = RootView.findViewById(R.id.textView_sum_ou);
overUnderTvH = RootView.findViewById(R.id.textView_sum_ou_h);
minTimeTv = RootView.findViewById(R.id.textView_sum_min);
maxTimeTv = RootView.findViewById(R.id.textView_sum_max);
vacationsTv = RootView.findViewById(R.id.textView_sum_vac);
sickTv = RootView.findViewById(R.id.textView_sum_sick);
headlineTv= RootView.findViewById(R.id.textView_sum_headline);
return RootView;
}
private void refreshData() {
if (prefs == null)
{
prefs = new Preference(GeneralAdapter.getContext());
}
String month = prefs.getString(Preference.CURRENT_MONTH);
MonthData monthData = Calculators.CalculateLocalData(MainActivity.db.getAllDays(month));
totalTimeFullTv.setText(monthData.getTotalTimeFull()); //Crash here
totalTimeNetTv.setText(monthData.getTotalTimeNet());
averageTimeTv.setText(monthData.getAverageTime());
overUnderTv.setText(monthData.getOverUnder());
if (monthData.getOverUnderFloat()<0)
{
overUnderTvH.setText(R.string.sum_over_time_neg);
overUnderTv.setTextColor(ContextCompat.getColor(GeneralAdapter.getContext(),R.color.negative_color));
}
else
{
overUnderTvH.setText(R.string.sum_over_time_pos);
overUnderTv.setTextColor(ContextCompat.getColor(GeneralAdapter.getContext(),R.color.positive_color));
}
minTimeTv.setText(monthData.getMinTime());
maxTimeTv.setText(monthData.getMaxTime());
vacationsTv.setText(""+monthData.getVacations());
sickTv.setText(""+monthData.getSick());
headlineTv.setText(month);
}
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
#Override
public void onAttachFragment(Fragment childFragment) {
super.onAttachFragment(childFragment);
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
#Override
public void onPauseFragment() {
}
#Override
public void onResumeFragment()
{
refreshData();
}
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
}
MainActivity viewPager:
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
int currentPosition = 0;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
FragmentLifecycle fragmentToHide = (FragmentLifecycle) adapter.getItem(currentPosition);
fragmentToHide.onPauseFragment();
FragmentLifecycle fragmentToShow = (FragmentLifecycle) adapter.getItem(position);
fragmentToShow.onResumeFragment(); //Crash start
currentPosition = position;
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Log:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: michlind.com.workcalendar, PID: 25038
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
at michlind.com.workcalendar.mainfragments.SummaryFragment.refreshData(SummaryFragment.java:99)
at michlind.com.workcalendar.mainfragments.SummaryFragment.onResumeFragment(SummaryFragment.java:147)
at michlind.com.workcalendar.activities.MainActivity.onPageSelected(MainActivity.java:84)
at android.support.v4.view.ViewPager.dispatchOnPageSelected(ViewPager.java:1941)
at android.support.v4.view.ViewPager.scrollToItem(ViewPager.java:680)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:664)
at android.support.v4.view.ViewPager.onTouchEvent(ViewPager.java:2257)
at android.view.View.dispatchTouchEvent(View.java:11776)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2962)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2643)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:448)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1829)
at android.app.Activity.dispatchTouchEvent(Activity.java:3307)
at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:68)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:410)
at android.view.View.dispatchPointerEvent(View.java:12015)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4795)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4609)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4147)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4200)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4166)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4293)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4174)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4350)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4147)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4200)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4166)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4174)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4147)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6661)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6635)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6596)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6764)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:186)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:325)
at android.os.Looper.loop(Looper.java:142)
at android.app.ActivityThread.main(ActivityThread.java:6494)
UPDATE:
I eventually used:
#Override
public void onPageSelected(int position) {
Fragment fragment = adapter.getFragment(position);
if (fragment != null) {
fragment.onResume();
}
}
At my MainActivity, and used onResume() at each fragment. And this solution for the adapter:
http://thedeveloperworldisyours.com/android/update-fragment-viewpager/
The problem is, that you are trying to access views too early: view hierarchy is not created at that point yet.
If you post an event, that would take place on the next frame, you are guaranteed, that view hierarchy would be already setup:
#Override
public void onResumeFragment() {
new Handler().post(new Runnable() {
#Override
public void run() {
refreshData();
}
});
}
I faced the same problem when I had implemented custom life cycles for ViewPager. I think you are using FragmentStatePagerAdapter to populate fragments with ViewPager. As we know FragmentStatePagerAdapter destroys all the fragments when they lose focus. We need to provide same object for every page using singleton pattern.
In your code, Fragment creation can be like below for singleton pattern.
private SummaryFragment mInstance;
private SummaryFragment() {
// Required empty public constructor
}
public static SummaryFragment newInstance(String param1, String param2) {
if(mInstance == null)
mInstance = new SummaryFragment();
return mInstance;
}
Doing this has solved my problem. If this does not work for you? Can you share your PagerAdapter class.
onResumeFragment() is getting invoked before the creation of all the views of this fragment.
Try recreating newInstance first and then invoke onResumeFragment of FragmentLifeCycle interface in your Activity.
ViewPager keeps several items on either side attached (i.e. fragments resumed), however FragmentPagerAdapter uses Fragment.setUserVisibleHint to indicate, which item is current. Leverage that instead.
Here's what to do to leverage user visible hint:
Remove the OnPageChangeListener.
Ditch the FragmentLifecycle interface.
Set your fragment like so:
(in Kotlin, but you'll get the gist)
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser && isResumed) {
// Do stuff.
}
}
override fun onResume() {
super.onResume()
if (userVisibleHint) {
// Do the same stuff.
}
}
More info
FragmentPagerAdapter.getItem is a factory method. It's always supposed to return you a new instance of a fragment. If you tried to cache them, remove the cache (1) and don't use getItem yourself (2).
Code that sometimes crashes and sometimes doesn't is a b**** to debug. This can be caused by reusing fragments when you're not supposed to.
A new fragment instance is not attached, has no reason to create views and will be garbage collected once you leave onPageSelected.
You are using OnPageChangeListener incorrectly. This is not a safe way to control view lifecycle events. You need to use PagerAdapter in conjunction with ViewPager and override its instantiateItem / destroyItem callbacks.
See this example: http://android-er.blogspot.com/2014/04/example-of-viewpager-with-custom.html
PagerAdapter is to ViewPager what ListAdapter is to ListView, you need both to make your system work correctly.
Use onViewCreated() callback method of fragment to update your data that way you are sure that all your views are laid out perfectly.
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
refreshData();
}
Using Handler can still be risky as you can't be sure the view are inflated or not.
PROBLEM
The lifecycle of a Fragment is independent. You cannot be sure that when an onPageSelected() gets registered, that fragment has already been laid out. It's is an asynchronous event. So you cannot rely on this callback.
But on the other hand, you cannot also rely only on the onResume(), since in a ViewPager, the pages adjacent to the currently visible page are preloaded.
SOLUTION
Principally you will need to refreshData() when the fragment is visible to user and actively running. The definition of onResume() says the same:
Called when the fragment is visible to the user and actively running. (...)
So simply call refreshData() in the onResume() of your fragment, and don't worry if you notice this getting called while the ViewPager wasn't really showing this page.
Like most people said you need to make sure your fragment is active and visible to the user. i had a similar problem. I used onHiddenChanged to decided when to reload data.
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
refreshData();
}
}
You should inflate your layout in onCreateView but shouldn't initialize other views using findViewById in onCreateView.
here is a code from the FragmentManager
// This calls onCreateView()
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState);
// Null check avoids possible NPEs in onViewCreated
// It's also safe to call getView() during or after onViewCreated()
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
It's better to do any assignment of subviews to fields in onViewCreated. This is because the framework does an automatic null check for you to ensure that your Fragment's view hierarchy has been created and inflated (if using an XML layout file) properly.
once the view is created then initialize your views.
Add this check in refreshData() method:
if (isAdded() && getActivity() != null)
I have tab1 and tab3 also these have their classes and I want to click button in tab1 and change textview in the tab3, but I couldn't find anyway.
This is my tab1 class
public class tab1Contacts extends Fragment{
TextView tv;
EditText et;
TextView tv3;
personInfo pı;
public personInfo returnpı(){
return pı;
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.tab1contents, container, false);
Button btn_jog = (Button) rootView.findViewById(R.id.jogging_button);
tv = (TextView) rootView.findViewById(R.id.newRecordText);
et = (EditText) rootView.findViewById(R.id.durationtext) ;
pı = new personInfo();
pı.eyesPower = 100;
pı.brainPower = 100;
pı.armsPower = 100;
pı.legsPower = 100;
pı.hearthPower = 100;
pı.energyLevel = 100;
pı.calorie = 2000;
pı.condition = 0;
btn_jog.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
int duration = Integer.parseInt(et.getText().toString());
pı.jogging(duration);
//I want to change here textview in the tab3.
}
});
return rootView;
}
}
This also my tab3 Class:
public class Tab3Contacts extends Fragment {
TextView tv3;
double newBrainpower;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.tab3contents, container, false);
tv3 = (TextView) rootView.findViewById(R.id.list_text) ;
return rootView;
}
}
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//pager.setCurrentItem(yourindex);// if you use pager
getTabHost().setCurrentTab(yourindex);
}
});
If I'm reading your question correctly, then what you need is for tab3 to listen to events from tab1. For that you will want to implement some kind of internal notification/eventing system. This is typically handled through a notification handling class that will register observers/listeners.
An example from a project I've been maintaining:
public class NotificationManager {
public interface Observer {
public void update(String notificationName, Bundle data);
}
private static NotificationManager singletonNotifier = null;
private HashMap<String, ArrayList<Observer>> mObservables = null;
private NotificationManager() {
mObservables = new HashMap<String, ArrayList<Observer>>();
}
//enforce singleton
public static NotificationManager getInstance() {
if (singletonNotifier == null) {
singletonNotifier = new NotificationManager();
}
return singletonNotifier;
}
public void addObserver(String notificationName, Observer observer) {
// add to map
// in multi-threaded apps make sure you use synchronized or a mutex
}
public void removeObserver(String notificationName, Observer observer) {
// remove from map; mind threading
// overload as necessary for your design
}
public void notifyObservers(String notificationName, Bundle data) {
// go through your map of observers, build an array of observers
// that need to update, then for each observer, call
// observer.update(notificationName, data);
}
}
Then your tab3 class would need to implement the Observer interface and on object construction register itself with the NotificationManager with a string value for the type of notification it wants (use best practices for constants instead of string literal arguments), using the call
NotificationManager.getInstance().addObserver("Tab1DataChange", this);
It will need to implement the update(String, Bundle) method, which will make all the changes that you need.
Then in the class for the tab1 object, add to the click listener this call:
NotificationManager.getInstance().notifyObservers("Tab1DataChange", data);
Where data is any information that observers would need to know to respond. In keeping with the idea of decoupling code, do not put together a data bundle that is explicitly for one listener, because at some point you might need something else to listen for the same event. Save yourself some grief now by designing the data bundle to contain what would need to update regardless of who is consuming the event.
Some lessons learned for me:
Pay attention to Android lifecycle. OnPause and OnDestroy for the active view(s) should unregister the listener so that you don't end up with a null pointer exception if something triggers that event while the observer object is not available. OnCreate and OnResume should reregister. In some cases I have been able to not worry about OnPause/OnResume, but depending on your app you may need them.
I have an Activity A with a fragment frag2. Inside the fragment I have a RecyclerView and Adapter to show a list of custom class objects. Adding objects to the adapter is handled programmatically. I have a button inside TwoFragment that opens a FragmentDialog. I'd like to add an object to my Adapter by confirming this dialog, but it seems that the adapter is null when called from the FragmentDialog.
The same adapter is not null, and works if I call it from the fragment OnClick.
Moreover the adapter is null only after screen rotation, it works fine before rotating.
To communicate between the two Fragments I implement a communicator class in activity A.
Activity A
public void respond(String type) {
frag2.addSupport(type);
}
frag2
public RecyclerView rv;
public ArrayList<support> supports;
public myAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supports = new ArrayList<>();
adapter = new myAdapter(supports);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = inflater.inflate( R.layout.fragment_two, container, false);
layout.setId(R.id.frag2);
if (savedInstanceState!=null)
{
supports = savedInstanceState.getParcelableArrayList("supports");
}
rv = (RecyclerView) layout.findViewById(R.id.rv);
adapter = new myAdapter(supports);
rv.setAdapter(myAdapter);
rv.setLayoutManager(new LinearLayoutManager(getActivity()));
rv.setItemAnimator(new DefaultItemAnimator());
#Override
public void onClick(View v) {
int id = v.getId();
switch (id){
case R.id.button1:
addSupport(type); // THIS WORKS ALWAYS, even after screen rotate
break;
case R.id.button2:
showDialog();
break;
}
}
public void showDialog(){
FragmentManager manager = getFragmentManager();
myDialog dialog = new myDialog();
dialog.show(manager, "dialog");
}
public void addSupport(String type){
adapter.addItem(new support(type)); // this line gives null pointer on adapter, but only if called after screen rotate and only if called from the dialog
}
dialog
communicator comm;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog, null);
comm = (myCommunicator) getActivity();
create = (Button) view.findViewById(R.id.button_ok);
create.setOnClickListener(this);
return view;
}
#Override
public void onClick(View v) {
if(v.getId()==R.id.button_ok)
{
// some controls to set type
comm.respond(type)
dismiss();
}
else {
dismiss();
}
myAdapter
public class myAdapter extends RecyclerView.Adapter<myAdapter.VH> {
private LayoutInflater inflater;
private ArrayList<support> data = new ArrayList<>();
// settings for viewholder
public myAdapter (ArrayList<support> data)
{
this.data=data;
}
public void addItem(support dataObj) {
data.add(dataObj);
notifyItemInserted(data.size());
}
}
logcat
FATAL EXCEPTION: main
java.lang.NullPointerException: Attempt to invoke virtual method 'myAdapter.addItem(myObject)' on a null object reference
I hope there are no mistakes, I shortened the code for better understanding. Keep in mind that everything works if I never rotate the screen.
I'm a beginner with android and I'm stuck with this for several days now. Please, help.
To understand the problem, it's as you say:
.. everything works if I never rotate the screen
So firstly to understand what happens on rotation, this is a quote from the Android Developer website:
Caution: Your activity will be destroyed and recreated each time the user rotates the screen. When the screen changes orientation, the system destroys and recreates the foreground activity because the screen configuration has changed and your activity might need to load alternative resources (such as the layout).
Ok, now to understand the error:
FATAL EXCEPTION: main
java.lang.NullPointerException: Attempt to invoke virtual method 'myAdapter.addItem(myObject)' on a null object reference
Essentially, in your dialog class, you have created a strong dependency by declaring :
comm = (myCommunicator) getActivity();
because comm references objects which would have been destroyed on rotation, hence the NullPointerException.
To further understand runtime changes, such as orientation changes, I'd recommend going through Handling Runtime Changes.
Update
Thank you for your answer, what would you recommend instead of comm = (myCommunicator) getActivity(); ?
The solution comes in 3 parts:
Make sure the onCreate of Activity A has the following:
#Override
public void onCreate(Bundle savedInstanceState) {
......
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
frag2 = (Frag2) fm.findFragmentByTag(“frag2”);
// create frag2 only for the first time
if (frag2 == null) {
// add the fragment
frag2 = new Frag2();
fm.beginTransaction().add(frag2 , “frag2”).commit();
}
......
}
Add setRetainInstance(true) to the onCreate of frag2.
Remove the implicit referencing i.e. comm = (myCommunicator) getActivity();, and implement something more loosely coupled for dialog.
dialog
public interface Communicator {
void respond(String type);
}
Communicator comm;
....
public void addCommunicator(Communicator communicator) {
comm = communicator;
}
public void removeCommunicator() {
comm = null;
}
#Override
public void onClick(View v) {
if((v.getId()==R.id.button_ok) && (comm!=null))
{
// some controls to set type
comm.respond(type);
}
// Regardless of what button is pressed, the dialog will dismiss
dismiss();
}
This allows you do the following in frag2 (or any other class for that matter):
frag2
<pre><code>
public class Frag2 extends Fragment implements dialog.Communicator {
........
public void showDialog() {
FragmentManager manager = getFragmentManager();
myDialog dialog = new myDialog();
dialog.addCommunicator(this);
dialog.show(manager, "dialog");
}
#Override
public void respond(String type){
adapter.addItem(new support(type));
}
}
I'm new in Android App developing via Java. I'm using Eclipse. If I create an Activity, Eclipse automatically generates a Placeholderfragment Class and Fragment.xml. Can I disable this function? Or is it not advisable to do that? I delete those files because I find it more complicated to use than just write in one xml file at the moment.
Second question is how do I implement a "starting Page" for my App? For example some sort of a logopage which automatically disables after a few seconds and switches to a new activity. Create a separate Activity for it or do I use something else?
Actually you need two activities, one startup Activity which is used to show your logo or some guide,the other is a MainActivity which should be started by the startUp Activity.
In short You can do something like this:
public class MainActivity extends FragmentActivity {
Fragment fragment;
String className;
#Override
protected void onCreate(Bundle savedInstanceState) {
Log.d("MainActivity", "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Store the name of the class
className=MainActivity.class.getSimpleName();
//First fragment should be mounted on oncreate of main activity
if (savedInstanceState == null) {
/*fragment=FragmentOne.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).addToBackStack(className).commit();
*/
Fragment newFragment = FragmentOne.newInstance();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, newFragment).addToBackStack(null).commit();
Log.d("FRAGMENT-A", "fragment added to backstack");
}
}
}
FragmentOne.java
public class FragmentOne extends Fragment{
String className;
public static FragmentOne newInstance(){
Log.d("FragmentOne", "newInstance");
FragmentOne fragment = new FragmentOne();
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("FragmentOne", "onCreateView");
View view=inflater.inflate(R.layout.fragment_one, container, false);
//Store the name of the class
className=FragmentOne.class.getSimpleName();
return view;
}
}
Let me know if you need any more info
Well, in a Single Activity setup, the way I did this was the following:
public class SplashFragment extends Fragment implements View.OnClickListener
{
private volatile boolean showSplash = true;
private ReplaceWith activity_replaceWith;
private Button splashButton;
public SplashFragment()
{
super();
}
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try
{
activity_replaceWith = (ReplaceWith) activity;
}
catch (ClassCastException e)
{
Log.e(getClass().getSimpleName(), "Activity of " + getClass().getSimpleName() + "must implement ReplaceWith interface!", e);
throw e;
}
startSwitcherThread();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_splash, container, false);
splashButton = (Button) rootView.findViewById(R.id.fragment_splash_button);
splashButton.setOnClickListener(this);
return rootView;
}
public void startSwitcherThread()
{
Thread splashDelay = new Thread()
{
#Override
public void run()
{
try
{
long millis = 0;
while (showSplash && millis < 4000)
{
sleep(100);
millis += 100;
}
showSplash = false;
switchToFirstScreen();
}
catch (Exception e)
{
e.printStackTrace();
}
}
};
splashDelay.start();
}
private void switchToFirstScreen()
{
activity_replaceWith.replaceWith(new FirstFragment());
}
#Override
public void onClick(View v)
{
if(v == splashButton)
{
if(showSplash == false)
{
switchToFirstScreen();
}
}
};
}
Where the ReplaceWith interface is the following:
import android.support.v4.app.Fragment;
public interface ReplaceWith
{
public void replaceWith(Fragment fragment);
}
And the replace function is implemented like so:
#Override
public void replaceWith(Fragment fragment)
{
getSupportFragmentManager()
.beginTransaction().replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit();
}
Now, most people will say this is not a good approach if you're using multiple activities, and/or using multiple orientations and aren't just simply displaying a single Fragment in a single Activity no matter what. And they are completely right in saying so.
Multiple orientations would require the Activity to be responsible for knowing what is the "next" Fragment at a given replace call, and where to place it (which container, or to start it in a new Activity). So this is a valid approach only if you are certain that you only have one container and there is one Fragment shown at a given time.
So basically, if this does not apply to you, then you need to utilize the same approach (make a specific delay before you replace the current Fragment or Activity with another one, this specific code allows you that once the splash has been shown once, then clicking the button will automatically take you to the next screen - typical game splash setup, really), but use activity callbacks specific to the Fragment in order to swap one out for the other.
A Fragment setup I recommend and isn't relying on this special case can be seen here: Simple Android Project or its equivalent on Code Review: Fragment Start-Up Project