I'm trying to make a ListFragment that will periodically be updated (Calls to update not shown here, but the update function is). Here's the log cat that I'm seeing:
04-05 11:05:44.252: V/QslLogFrag(2690): onCreate()
04-05 11:05:44.256: V/QslLogFrag(2690): onCreateView()
04-05 11:05:44.304: V/QslLogFrag(2690): onActivityCreate()
04-05 11:05:44.612: V/QslLogFrag(2690): null
04-05 11:05:44.736: V/QslLogFrag(2690): com.kd7uiy.qslmapper.QslLogAdapter#5282f55c
04-05 11:05:47.948: V/QslLogFrag(2690): null
The last 3 lines lines are all provided in the updateView class, which is called externally when there's new data to check. The first two are called essentially automatically, the third one is called by user input.
And the code:
public class QslLogFragment extends ListFragment {
private CursorAdapter mAdapter;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getListView().setFastScrollEnabled(true);
Cursor cursor = QslDatabase.getInstance(getActivity()).getTableQuery(
mFilters);
mAdapter= new QslLogAdapter(getActivity(), cursor, 0);
setListAdapter(mAdapter);
Log.v(TAG,"onActivityCreate()");
}
public void updateView() {
Cursor cursor = QslDatabase.getInstance(getActivity()).getTableQuery(
mFilters);
Log.v(TAG,""+getListAdapter());
if (getListAdapter() != null) {
((CursorAdapter) getListAdapter()).changeCursor(cursor);
}
}
Somehow it seems to me as if the adapter is getting set to null. Note I can still scroll through the ListView without problems, but I can't update it, with a new set of filters. Any ideas what's happening?
Note, QslAdapter extends CursorAdapter, and I'm using the Support Library CursorAdapter.
Here's a list of things I've tried:
I have tried just using setListAdapter and getListAdapter.
I have tried not using getListAdapter, but just keeping a reference to mAdapter.
I have verified that there is only one instance of this Fragment.
I am not calling setListAdapter anywhere.
I've verified that getListAdapter and mAdapter always report the same pointer, or null.
If #commonsware doesn't mind, I'll put this into an answer for prosperity. Although I'm sure if he has any further insights, he will create a much better answer.
I had the feeling you could be dealing with multiple copies of your QslLogFragment. I have faced similar problems previously.
In situations like this, it is a good idea to log your method calls, like you have done. Where it is "impossible" for the adapter to be different, that is the hint of having 2 fragments, hence the suggestion to log the fragment as well.
Place extra logs in ALL the Activity & Fragment lifecycle methods to track down the final piece of the puzzle. It may seem like overkill, but you could find some surprises.
In the case that I had, which you reminded me of, I had based my code on an Android SDK sample that actually created a new fragment each time I tried to access it. I had not cached any of the fragments that were previously created. So check the code that retrieves (or creates) your fragment.
Here is the code from my FragmentPagerAdapter that creates a new fragment each time. This is bad code, and I don't recommend it, but for now it serves its purpose in the demo it comes from.
public Fragment getItem(int position)
{
// TODO don't create a new fragment each time this is called
switch (position)
{
case 0:
{
return new WindFragment();
}
// more cases... etc...
default:
{
return new TemperatureFragment();
}
}
}
This is called every time the user flips to a new page, to show a different fragment. This code caused an issue very similar to yours.
My issue was very close to Richard's answer, but I'll post a better version of the code.
I have been using a function called TabsAdapter to manage my FragementPager. The default code is this:
#Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
}
I was setting a listener to the code by using the getItem function (My private listener), and thus was actually creating a new fragment!
The solution was to use a WeakHashMap to store previous instances of the fragment so I don't re-create them if I don't have to!
WeakHashMap<Integer,Fragment> mFragmentMap;
#Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
if (mFragmentMap.get(position)==null){
mFragmentMap.put(position,Fragment.instantiate(mContext, info.clss.getName(), info.args));
}
return mFragmentMap.get(position);
}
Related
I create the initial content with the data from an arraylist(i get it from firebase)
the data changes every 15 minutes, how do i call a recreation or how do i settext from the mainactivity to the fragments / how do i update the textviews in the fragments (any of those would work please)
i've read a looot of people solving this with getItem but remember i have FragmentStateAdapter i don't have those methods viewPager2.getAdapter().notifyDataSetChanged() does nothing
Overridable Methods in FragmentStateAdapter
and then i thougth how about an interface just to change the text in my textviews but it always calls an error about a null pointer because i understand that the fragment is being "paused" while i don't see it because of the cycle and so and so
Calling a recreation of the viewPager2.setAdapter(new PagerAdapter() works but only when the gods want? and i want something to force it or to be relaiable
(Sincerelly i don't know anymore why am i using the newest viewpager2 and PagerAdapter FragmentStateAdapter as Google suggests) should i try my way around viewpager and all those old tools?
As per Doc we can update the fragment by calling notifyDatasetChanged() therefore by creating a function(setData) in Adapter and inside which calling notifyDatasetChanged() may be work..like below
fun addData(data: ArrayList<>) {
mData = data
notifyDataSetChanged()
}
I encountered this problem, my solution is: save the fragment instance using WeakReference in adapter, and invoke the method in fragment when needing to update it's data.
The code is like this:
public MyAdapter extends FragmentStateAdapter{
private Map<Integer, WeakReference<MyFragment>> fragmentMap = new HashMap();
#Override
public Fragment createFragment(int position) {
MyFragment fragment=new MyFragment();
fragmentMap.put(position, new WeakReference<>(fragment));
return fragment;
}
public void updateData(List<String> dataList){
for (int position : fragmentMap.keySet()) {
WeakReference<MyFragment> wr = fragmentMap.get(position);
if (wr != null && wr.get() != null) {
MyFragment fragment = wr.get();
fragment.updateData(dataList.get(position));
}
}
}
}
public MyFragment extends Fragment{
public void updateData(String data){
...
}
}
Now you can invoke updateData(List dataList) on your adapter to update data, and it is using WeakReference to avoid memory leak in the code.
The core of this question is how to send a MatrixCursor of data from an activity to a fragment.
I am doing my search functionality in my activity and am returning a fragment which contains a list that will be filled with data from the query response that is a Matrix Cursor.
Bundle and parcelable thus far are not working out for me. Any tips or guidance?
I see three potential options.
Try Gson. You may be able to convert the instance to a String to pass it and then reinstantiate it from the String data. However, this doesn't work for everything.
Create a new method in your Fragment. You're not meant to pass custom arguments in the constructor, but you could pass it later:
private MatrixCursor cursor;
public void setCursor(MatrixCursor cursor) {
this.cursor = cursor;
}
Since it's the same instance, changes made to the one in your Fragment will be reflected in your Activity. However, this will cause issues if you rotate your device or cause another configuration change. To fix that, add the following to your <activity> attribute in your Manifest:
android:configChanges="orientation|keyboardHidden"
Fragments retain a reference to their parent Activity. You could add helper methods to your Activity that essentially proxy the ones you need from your MatrixCursor instance:
public void addRow(Object[] columnValues) {
cursor.addrow(columnValues);
}
//etc
Then, in your Fragment, you can do:
((MyActivityClass) getActivity()).addRow(columnValues);
Option 3 would probably be the best option, since it doesn't rely on something that might not work or what's basically a hack.
Make a interface Searchable
public interface Searchable {
MatrixCursor getSearchResult()
}
make sure you implement this interface to your activity.
public MainActivity extends AppCompatActivity implements Searchable {
private MatrixCursor mSearchResultMatrixCursor;
...
#Override public MatrixCursor getSearchResult() {
return mSearchResultMatrixCursor;
}
}
In you Fragment's onCreate or wherever you want to use MatrixCursor,
you can call,
if(getActivity != null && getActivity instanceOf Searchable) {
MatrixCursor matrixCursor = ((Searchable)getActivity).getSearchResult()
}
This will persist as long as activity is not recreated.
I have 5 fragments in ViewPager used to fill business object with several fields step by step, in each step some of those fields will be set. I've read many articles about communication between fragments but I'm not feeling comfortable the way others preferred, so after thinking about HOW should I do this in my case, finally I start thinking to use singleton model object which all fragments can easily access to its fields and fill them in specific steps.
As I'm new to android I want to hear from experts about using singleton instead of passing data between fragments such as implemented interface(It seems its so complicated and hard to maintenance). Any advice will be helpful.
While singleton approach seems easy to implement and understand it is way not to best way to achieve what you need. One reason is that your model object or as you call it business object lives outside of your activity's context which can create hard to find bugs. E.g. in case when more than one instance of your activity class is created by system and both keep reference to your singleton. See how you lose track of your objects?
What I would do is
Make my model object to implement Parcelable you will hate it at the beginning but once you get use to it it will become your model's best friend
Since your model is parcelable now you can easily pass it between fragments, activities, and even save it in shared preferences. One important thing to note here when you pass your parcelable between fragment or activity it is like pass by value, i.e. every time new instance is created.
Set your fragment's argument or if it is already instantiated then get arguments and add your model. here is an example:
if a fragment is not active yet:
Bundle args = new Bundle();
args.putParcable("businessObject", yourBusinessObjectThatIsParcable);
yourFragment.setArguments(args);
Otherwise:
yourFragment.getArguments().putParcelable("businessObject", yourBusinessObjectThatIsParcable);
In your fragment perhaps in onCreateView method get your model object like this MyParcableObject mpo = (MyParcableObject)getArguments().getParcelable("businessObject") and use it set whatever data you want.
When you finish editing your object on button click or in onPause method updated your fragment's arguments same way getArguments().putParcelable("businessObject", mpo);
in your last page or last fragment you can pass your object to your activity, here is how to do it
Even though it looks cumbersome but it is a practice that you need to get used to as an android developer. You get lot more control when your model implements parcelable.
Another way to do what you need is thru Delegation Pattern but it is mostly used for callbacks even though you can pass objects as well.
I wouldn't recommend a global singleton. There are two main reasons:
By definition, a singleton limits your app to a single instance of the main business object. If you (or a designer, or your boss's boss's boss) ever decide to have multiple of these ViewPagers at a time, you will have to change your architecture anyways.
The "Android way of thinking" is to expect that your user may put your app in the background and use other apps before returning to your app. If the system decides to kill your app in the background, then your singleton memory object will be destroyed, and your user will have lost all of their progress. The correct Android way to save state is by keeping the state in an Activity or Fragment, saving it appropriately in onSaveInstanceState(), and restoring it in onCreate().
All of the Fragments in the ViewPager can get a reference to the parent Activity via a call to getActivity(). Or if your ViewPager is within a Fragment, then all of the Fragments can access the parent Fragment via a call to getParentFragment(). You can then cast the result to the appropriate class (or better yet, interface) and make method calls to pass data back and forth. Keep track of your business data in the parent Activity/Fragment. This way, you don't need a global singleton
For example,
public class MyParentFragment extends Fragment {
private String mPageOneData;
private int mPageTwoData;
private List<Date> mPageThreeData;
public void setPageOneData(String data) {
mPageOneData = data;
}
...
}
public class PageOneFragment extends Fragment {
private void sendDataToParent(String data) {
Fragment f = getParentFragment();
if (f != null && f instanceof MyParentFragment) {
MyParentFragment parent = (MyParentFragment) f;
f.setPageOneData(data);
}
}
}
you can save your data in onSaveInstanceState() event of the activity in case your process will go into the background.
you can restore your data in onCreate() event by using Bundle and getExtras().
you can save your data in application class and the data will still be there in case your process will go into the background.
i prefer the first option because you don't want to make a mess in the application class with all the data from different activities and fragments.
I hope i could help :)
Have you checkout EventBus?
I'm not sure if it is the best approach, specially when your question is too broad, however it will be cool with just 5 fragments.
Hope it helps
I suppose in your MainActivity there is a ViewPager, and FragmentOne will be one of the fragments inside the view pager. Here the MainActivity is communicating to the FragmentOne to refreshhis adapter. Hope is clear.
In your MainActivity add this interface:
public interface Updateable {
public void update();
}
Implement this interface in a fragment that needs to be updated, and write the code to notify the adapter inside the update method:
public class FragmentOne extends Fragment implements MainActivity.Updateable {
...
#Override
public void update() {
// YOUR CODE TO UPDATE HERE, FOR EXAMPLE, HERE I'M UPDATING THE ADAPTER
if ( adapter != null ) {
adapter.notifyDataSetChanged();
} else {
Log.d("LOG_TAG", "null");
}
}
...
}
Call the update method from the MainActivity when the fragment loads first. You can do this overriding the getItemPosition method in your PagerAdapter, like this:
#Override
public int getItemPosition(Object object) {
if ( object != null && object instanceof FragmentOne ) {
FragmentOne f = (FragmentOne) object;
f.update();
}
return super.getItemPosition(object);
}
Finally, you have to call notifyDataSetChanged() of your viewPager adapter. This will force the adapter of your viewpager to call the getItemPosition method.
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
int previousState;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (previousState == ViewPager.SCROLL_STATE_SETTLING && state == ViewPager.SCROLL_STATE_IDLE) {
if ( viewPagerAdapter.getItem(viewpager.getCurrentItem()) instanceof Pictures ) {
Log.d("LOG_TAG", "New Position=" + viewpager.getCurrentItem());
viewPagerAdapter.notifyDataSetChanged();
}
}
previousState = state;
}
});
Before choosing any option, keep in mind user can navigate or open any other app(s) so you lost your data.
You can use onSaveInstanceState but it will somehow difficult to maintain (as you said you are new in android). You can go with with singleton by using
Database - Use when you want to store maintain multiple records but you have to create a database getter/setter or use any ORM like RushOrm etc.
SharefPreference(preferably) - If you want to use single values.
In both cases you will create a singleton object and access its properties in your fragments.
make your objects parcelable and then pass it to other fragments using bundle. i.e bundle.putParcelable(obj) parcelable is very efficient and fast.
it should motivate you
http://www.developerphil.com/parcelable-vs-serializable/
I have a list like fragment, and currently I am passing in info like so:
Fragment:
public void populate(Map<String, List<Book>> booksGroupedByType)
{
BookListAdapter bookListAdapter = new BookListAdapter(this.getActivity(), booksGroupedByType);
_lstBooks.setAdapter(bookListAdapter);
}
Activity:
private void populateBooksFragment()
{
Map<String, List<Book>> booksGroupedByType = _repository.getAllBooksGroupedByType();
BookListFragment bookListFragment = (BookListFragment) getFragment(R.id.fragBooks);
if (bookListFragment != null)
{
bookListFragment.populate(booksGroupedByType);
}
}
Then I felt it would be better if I could pass this information when creating the fragment, since we have no constructor available I looked up the method and found this:
public static DetailsFragment newInstance(int index) {
DetailsFragment f = new DetailsFragment();
// Supply index input as an argument.
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);
return f;
}
I tried to implement, but found my Map was not serializable as it was and needed more work. So my question is, why go through this? is there a disadvantage to using my original approach (populate), which would even be faster than serializing?
I thought perhaps my fragment will lose its data when rotated, but no, when rotating (in emulator) the list was kept intact.
Let's say you have some data obtained in time/resource consuming way. If you don't want to download them each time configuration changes (and activity is destroyed), you have to somehow persist them.
First option is to put data into the bundle, so it will be available for the fragment even after it is autorecreated by the system. It may work for simple types, but for arbitrary object it's usually not an option because of performance reasons (serialization/parcelization).
Second option would be retaining the fragment, by setting a flag in fragment's onCreate():
setRetainInstanceState(true)
In that case fragment won't be destroyed after configuration change, but just detached from activity being destroyed, and attached to the new one. Any data you will pass e.g. via setters will be available too.
See also: Understanding Fragment's setRetainInstance(boolean)
I am working on an application using viewpagerindicator.
In my main activity that has the viewpagerindicator, I spin off a thread that does some computation and updates a an instance variable mString of the activity. I want to update a fragment in the viewpagerindicator with the mString. However, I can't seem to figure out the best way to reach the fragment.
Does anyone know of any good samples that do something similar to this?
Create a callback object in your Fragment, register it with your FragmentActivity. If mString is already set in FragmentActivity then you can return it immediately via the callback, otherwise, when the computation thread finishes, it can return the string via the callback. The callback method should do whatever the Fragment needs to do with the string, e.g. set the text of a TextView.
E.g. create an interface called DynamicDataResponseHandler as follows:
public interface DynamicDataResponseHandler {
public void onUpdate(Object data);
}
Then in your Fragment, implement that interface as follows:
private class MyStringDataResponseHandler implements DynamicDataResponseHandler {
#Override
public void onUpdate(Object object) {
mYourTextView.setText((String)object);
}
}
Your Fragment can then instantiate a MyStringDataResponseHandler object in its onCreate, pass that to the FragmentActivity via a method in the FragmentActivity like:
private MyStringDataResponseHandler mMyStringDataResponseHandler;
public void registerMyStringDataResponseHandler (DynamicDataResponseHandler callback) {
mMyStringDataResponseHandler = callback;
if(mString != null) {
mMyStringDataResponseHandler.onUpdate(mString);
}
}
And wherever in your Handler you obtain the value for mString, do something like this:
if(mMyStringDataResponseHandler != null) {
mMyStringDataResponseHandler.onUpdate(mString);
}
Do some reading on the concept of Callbacks to get a better understanding of what I'm doing above and other ways you can use them.
You want to update the UI of a Fragment in ViewPager after it is started, do i make it clear?
Ok, in this situation
You should add a public method in your custom Fragment.
Find the Fragment in your Activity.
Invoke the method after your calculation is done.
The question is same with this one.