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.
Related
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 want to pass a data object to Fragment, here are two ways to do this:
public class MyFragment extends Fragment {
private Serializable way1;
private Serializable way2;
public void setDataWay1(Serializable way1) {
this.way1 = way1;
}
public void setDataWay2(Serializable way2) {
Bundle data = new Bundle();
data.putSerializable("data", way2);
setArguments(data);
}
}
So, what is the difference between the 2 ways? Sometimes, way1 may cause NullPointerException,why? If I want to pass a OnClickListener to Fragment, what should I do?
While both methods can set the appropriate data to your fragment for first time initialization. Note that fragments will be recreated and destroyed by the system (for example on screen rotation). When that happens the system will not really call the setter way (method 1) hence, it will be a giant FAIL. Therefore, it is recommended to use the setArguments() way.
I have multiple fragments in ViewPager. How can i get fragment first EditText Data to last Fragment?
I have set value in my first fragment like below -
txtConsAcNo.setText(account_no);
txtMeterSrMo.setText(mtr_serial_no);
Now i am getting this txtConsAcNo, txtMeterSrMo value on my last fragment like below-
ConDetFirstFragment f1 = new ConDetFirstFragment();
txtConsAcNo = f1.txtConsAcNo.getText().toString();
txtMeterSrMo = f1.txtMeterSrMo.getText().toString();
Now what i want that i am getting Null value and my app get unfortunately stopped. i want to get this data to my last fragment without bundle. how can i achieve this ?
Very Easy to Achieve this without Creating Interface, Bundle or intent -
I have declared all the variables in all the fragment "Public Static" like Below -
public static EditText txtConsAcNo, txtMeterSrMo;
After on any fragment i have declared variable to get data like below-
public static String txtConsAcNo,txtMeterSrMo;
Now i have created function to get value from first fragment in above variable below-
public static void getalldata(){
ConDetFirstFragment f1 = new ConDetFirstFragment();
txtConsAcNo = f1.txtConsAcNo.getText().toString();
txtMeterSrMo = f1.txtMeterSrMo.getText().toString();
}
Happy Coding...
There are a couple of problems here:
The first fragment may have been destroyed by the Android system to conserve memory.
Your fragments should not talk to each other directly
To achieve what you need, you need to jump through a few hoops.
Assuming that the source texts are EditText objects (ie. editable by the user), then add a TextWatcher to each of the EditText objects.
Create an Interface:
public interface TextPurveyor {
void setText1(String t);
String getText1();
void setText2(String t);
String getText1();
}
Implement this interface in the host Activity; and save the text values locally in the activity. Don't forget to save/restore them with the rest of the Activity state.
Make the TextWatcher objects call the appropriate setText(..) methods on the host activity:
((TextPurveyor)getActivity()).setText1(...);
Make each fragment check that the host activity implements this method.
When the second fragment wants a string, ask the activity for it:
((TextPurveyor)getActivity()).getText1();
To avoid coupling your project code tightly, try to use the design patterns that have been proven to work best like the Publisher/Subscriber as I will show you below:
There is a popular library I have always used in my projects called EventBus - just add the following to your build.gradle (module-level) file under dependencies :
compile 'org.greenrobot:eventbus:3.0.0'
Secondly, create a simple Plain Old Java Object (POJO) to represent your Event:
public class FragmentAToLastEvent{
private String txtConsAcNo;
private String txtMeterSrMo;
FragmentAToLastEvent(String acNo, String srMO){
this.txtConsAcNo = acNO;
this.txtMeterSrMo = srMO;
}
//getters and setters if needed
public String gettxtConsAcNo(){
return txtConsAcNo;
}
public String gettxtMeterSrMo(){
return txtMeterSrMo;
}
}
Next step is to actually use your Event class here:
So, in your fragment that you want to send text from EditText, simply do this:
String txtConsAcNo = f1.txtConsAcNo.getText().toString();
String txtMeterSrMo = f1.txtMeterSrMo.getText().toString();
EventBus.getDefault().post(new FragmentAToLastEvent(txtConsAcNo, txtMeterSrMo));
In your last fragment, simply do this to complete:
Inside onCreate or onAttach of your Fragment:
//register your event - making this class a subscriber
EventBus.getDefault().register(this)
//next, override a single method to receive the values you passed from above code (Fragment 1?)
public void onEvent(FragmentAToLastEvent event){
String txtConsAcNo = event.gettxtConsAcNo();
String txtMeterSrMo = event.gettxtMeterSrMo();
//now you can use your text here without problems!
}
Finally, remember to unregister inside onDestroy:
#Override
public void onDestroy(){
super.onDestroy();
EventBus.getDefault().unregister(this);
}
This is what I have always done and it is cleaner, without using interfaces that your fragments MUST implement and do all that!
I hope you find it helpful to you and good luck!
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.
Is this possible? I am trying to open a SQLite database cursor in one activity, and pass it to another activity.
Another way to do this which might be easier is to create an Application class for your app. This is guaranteed to be created only once, and exists for the lifetime of your app. Among other things, it can provide a "data hub" capability for your app so different Activities can share data easily. So, for your cursor, you'd simply use a member variable of the Application class like so (warning, I copied this code from my app and edited it here, so no guarantee of compilation. Just something to show the idea.):
package com.jcascio.k03;
import android.app.Application;
import android.database.Cursor;
// use your application's name instead of "K03Application"
public class K03Application extends Application {
public final String TAG = "K03";
Cursor sharedCursor; // this cursor can be shared between different Activities
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onTerminate() {
super.onTerminate();
}
public Cursor getSharedCursor()
{
return this.sharedCursor;
}
public void setSharedCursor(Cursor c)
{
this.sharedCursor = c;
}
}
The application object can be fetched from any Activity using
this.getApplication()
// You cast it to your Application sub-class and call the Cursor accessor function
Cursor c = ((K03Application)this.getApplication()).getSharedCursor();
So, your first Activity would fetch some goo from the database, which is returned to it as a Cursor. This activity would call setSharedCursor in the application. Then it would launch the second Activity, which would call getSharedCursor in its onCreate function (or any other function for that matter) to retrieve the cursor.
I personally don't know of any simple ways to do this. It might be easier just to make the query again in the destination activity.
You should write your own Cursor which will implement Parcelable interface. In this case you can put your cursor to parcel and send it to another Activity through putExtra(). In target Activity you can explode (in fact just find it through handler) Cursor through one of Parcel methods (related to Binder).