Fragments and ViewPager in Android - java

I am having a ViewPager to allow the user to slide between 5 different views where each "view" extends Fragment.
I have my own adapter which extend FragmentPagerAdapter and which implement getItem() as
#Override public Fragment getItem(int position) {
switch(position) {
case 0:
return new TextDescriptionFragment();
// Handle the 4 other cases the same way
}
}
This works fine and the user can swipe between the 5 different views. But here comes the problem: Each of the first 4 views contains Views such as Button and EditText which the user can interact with.
And I then want the last page(Page number 5) to show all the user input values from all the views from the 4 previous pages(fragments). How do I do that?
I can't find any way to read the user input values from the previous fragments. The views may not even exist anymore(But will be recreated if the user goes back).
And I can't seem to get the existing fragments.

I would consider having a custom object that keeps the data each fragment fills. Something like:
public class FillerData implements Parcelable {
private String page0$data0;
private String page0$data1;
private String page0$data2;
// getters and setters if you wish
// implement Parcelable interface as this object will be managed by host activity
}
You'll have only one such object managed by parent activity and the parent activity will implement an interface for exposing this object:
public static interface FillerDataExposer {
public FillerData exposeFiller();
}
public class MyFragmentHostActivity extends FragmentActivity implements FillerDataExposer {
private static final String FILLER_KEY = "FILLER_KEY";
private FillerData myFillerData;
protected void onCreate(Bundle savedInstance) {
.......
if(savedInstance != null) {
myFillerData = (FillerData) savedInstance.getParcelable(FILLER_KEY);
} else {
myFillerData = new FillerData();
}
}
protected void onSaveInstanceState(Bundle savedInstance) {
super.onSaveInstanceState();
savedInstance.putExtra(FILLER_KEY, myFillerData);
}
public FillerData exposeFiller() {
return this.myFillerData;
}
}
Now, each of your fragments will have access to that centralized data filler object through parent activity. To reduce the weight of your code, all your fragments could extend from a base fragment class that provides access to FillerDataExposer implementation (actually, the parent activity):
public abstract class AbstractFillerFragment extends Fragment {
protected FillerDataExposer dataExposer;
public void onAttach(Activity act) {
super.onAttach(act);
// make sure no ClassCastExceptions
this.dataExposer = (FillerDataExposer) act;
}
}
Fragments that should only record the data filled could look like this:
public class Page1Fragment extends AbstractFillerFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = // inflate XML resource ...
yourEditText = (EditText) view.findViewById(...);
// other relevant code ....
}
public void onViewCreated(View view, Bundle savedInstanceState) {
yourEditText.setText(dataExposer.exposeFiller.setPageX$DataY());
// some code for EditText.addTextChangedListener(new TextWatcher() could look like:
yourEditText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
dataExposer.exposeFiller().setPage1$Data0(s.toString());
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
}
}
While the fragment that needs to have access to all data stored could look like this:
public class FinalFragment extends AbstractFillerFragment {
public void collectAllData() {
DataFiller allDataCollectedObject = dataExposer.exposeFiller();
// by calling get...() you should have access to collected data.
}
}
This only a sketch, but you'll get the picture. The idea is to keep a single object in your activity managed across activity restarts and to make it accessible through interfaces so you will respect the fragment to activity patterns.
Hope it makes sense ...

2 solutions come to my mind.
The first one is to save user input data when your first 4 fragment's onpause() methods are called. You may save the data to a preference and then retrieve it from your 5th fragment.
The second approach is to persist your fragments while swiping.This way the swiping will be faster and cleaner and they want be recreated everytime a swipe happens:
yourcustomviewpager.setOffscreenPageLimit(5);
About setOffscreenPageLimit from the android doc:
Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number of pages you will need to support or have lazy-loading mechanisms in place on your pages, tweaking this setting can have benefits in perceived smoothness of paging animations and interaction. If you have a small number of pages (3-4) that you can keep active all at once, less time will be spent in layout for newly created view subtrees as the user pages back and forth.
You should keep this limit low, especially if your pages have complex layouts. This setting defaults to 1.

Related

ViewHolder from Adapter from Fragment starts Activity, how can the Activity talk back to the Fragment?

PlaylistFragment starts an adapter:
playlistsAdapter = new PlaylistRecyclerAdapter(playlistsListArray, addToPlaylist, mSong, getActivity(), this);
PlaylistRecyclerAdapter binds data to the PlaylistViewHolder, something like this:
((PlaylistViewHolder) viewHolder).bind(this, dataSet.get(position), addToPlaylist, mSong);
User clicks on an item in PlaylistViewHolder:
context.startActivity(PublicPlaylistActivity.createStartIntent(context, playlist));
Now here is the question, how can PublicPlaylistActivity talk back to the initial PlaylistFragment?
I suggest you'd better use Interface from fragment to adapter. So when user clicks anything in adapter, call function realization in fragment. If you need your activity to proceed some operation - ((YourActivity) getActivity()).someMethod() should be called from fragment.
Second trick is using broadcastreceiver to send events. A bit more complicated. You have to launch broadcast in view you need to recive message and send these messages from adapter. This approach is more complexible to debug and support if system is wide spread, so you'd better use interfaces.
There are several ways of doing that. The simplest way should be starting the PublicPlaylistActivity with startActivityForResult. In that way, then the activity finishes, you can set send some data to the caller fragment (which is PlaylistFragment in your case). Here is a nice tutorial about the implementation.
Another way of doing that is by using lifecycle methods. You might have a public static variable which can keep track of some status that you might observe in your onResume function of your PlaylistFragment when you are returning back from your PublicPlaylistActivity. You might consider a sample implementation as follows.
Define a public static variable in your PlaylistFragment. Then in your onResume function check the value of that variable and take actions accordingly.
public static boolean someIndicator = false; // Initialize with a default value
#Override
protected void onResume() {
super.onResume();
if(someIndicator == true) doSomething();
else doSomethingElse();
}
Now you can set the indicator variable from anywhere in your application actually which will have the effect on your PlaylistFragment. For example, from your PublicPlaylistActivity, you might consider doing something like this.
public void someFunctionInYourPublicPlaylistActivity() {
// ...
// Some code and then the following
PlaylistFragment.someIndicator = true;
}
Another way of achieving the same thing is by using a BroadcastReceiver. Here is a tutorial on how you can implement one.
It really depends on how you are structuring your whole activity-fragments communication. Hope that helps!
I would do a common "context" class (ComContext) with an interface. When you create your fragment, you also create this class. And from the activity you can check if it exists or not.
I assume that you already have a helper(AppHelper) class with static variables.
public class AppHelper {
public static ComContext comContext = null;
}
public class MainFragment {
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ConContext comContext = new ComContext();
comContext.listener = this;
AppHelper.comContext = comContext;
}
#Override
public void onDataChanged() {
}
#Override
public void onDestroyView() {
super.onDestroyView();
AppHelper.comContext = null;
}
}
public class MainActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (AppHelper.comContext != null) {
AppHelper.comContext.listener.onDataChanged();
}
}
}
public class ComContext {
public interface HelperListener {
void onDataChanged();
}
public HelperListener listener = null;
}

How do I communicate between a class and a fragment which uses it?

I'm using Android Studio. I haven't been able to find an answer online, so even a link to a solution would be helpful.
I have an Activity, which includes a number of Fragments. One of these Fragments is called BookGridFragment, which uses a class called BookGrid.
BookGridFragment looks like this (I've left out irrelevant bits):
public class BookGridFragment extends Fragment {
BookGrid myBookGrid;
public BookGridFragment() {}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// Inflate layout
View rootView = inflater.inflate(
R.layout.fragment_book_grid, container, false);
myBookGrid = rootView.findViewById(book_grid);
return rootView;
}
public void setBook(Book thisBook) {
myBookGrid.setBook(thisBook);
}
}
The BookGrid class is:
public class BookGrid extends View {
private Book mBook;
public BookGrid(Context thisContext, AttributeSet attrs) {
super(thisContext, attrs);
}
public void setBook(Book newBook) {
mBook = newBook;
}
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
}
public boolean onTouchEvent(MotionEvent event) {
// This function responds to the user tapping a piece of
// book info within the grid
// THIS IS WHERE I'M HAVING PROBLEMS
}
}
So, that all works fine. The issue is, that I need the BookGridFragment to know when the user touches the BookGrid and to pass that information to another Fragment (via the Activity). So, I assume that when the onTouchEvent is reached, that should somehow notify the BookGridFragment that the BookGrid was touched, but I can't figure out how to do that.
Everything I've found online is about passing information between Fragments, but that approach doesn't work here as the BookGrid class doesn't "know" that it's within a BookGridFragment.
You could use the same idea that is used to communicate a Fragment and an Activity. Create an interface:
public interface OnBookGridTouched{
void onTouchGrid();
}
Add a variable to your BookGrid:
private OnBookGridTouched mCallback;
Add a setter to this variable:
public void setCallback(OnBookGridTouched callback){
mCallback = callback;
}
Then make your fragment implement the interface:
public class BookGridFragment extends Fragment implements OnBookGridTouched {
You'll be forced to implement the method onTouchGrid
In your fragment onCreateView pass the fragment to your custom view:
myBookGrid.setCallback(this);
Finally, in your custom view you can call the callback to reference the fragment:
public boolean onTouchEvent(MotionEvent event) {
// This function responds to the user tapping a piece of
// book info within the grid
// THIS IS WHERE I'M HAVING PROBLEMS
mCallback.onTouchGrid();
}
A solution could be to set the onTouch/onClick listener in the fragment instead of in the BookGrid itself. From there you can use the fragment method getActivity() to call an activity method, parsing on the correct data to the correct fragment.
I think this situation is very similar to a Fragment containing a Button.
The Button has a method which accepts something implementing a certain interface (for the Button: View.OnClickListener). The Fragment calls that method (for the Button: setOnClickListener()) to pass in the desired Object implementing all the required methods, either an anonymous class or maybe a field or the Fragment itself. There are pros and cons for all three approaches, it depends on your situation which one is best.
They have in common that BookGrid should have an interface as well as a method so other classes can set the current Object implementing that interface.
I am not quite sure about the exact scenario that you are having there. However, if the problem is the communication between the fragment and an activity which hosts the fragment, then you might think of the following implementation.
Let me point out some of your concerns first.
Everything I've found online is about passing information between
Fragments, but that approach doesn't work here as the BookGrid class
doesn't "know" that it's within a BookGridFragment.
BookGrid class will know the context of its existence when you will pass the Context towards it while calling a function of it. So I would like to suggest passing the context of the Activity or Fragment when you are calling a function from your BookGrid class.
public class BookGrid extends View {
private Book mBook;
private Context context;
public BookGrid(Context thisContext, AttributeSet attrs) {
super(thisContext, attrs);
this.context = thisContext;
}
public void setBook(Book newBook) {
mBook = newBook;
}
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
}
public boolean onTouchEvent(MotionEvent event) {
// Call the function of your host activity
((YourActivity)(thisContext)).onBookGridTouched();
}
}
Now write a public method in your activity class which hosts the fragment named onBookGridTouched.
public void onBookGridTouched() {
// Communicate with other fragments here
}
However, a noble approach of solving this problem in a more generic way is to use an interface and then implement the interface wherever necessary like #LeviAlbuquerque suggested.
I am just putting another workaround which is a bit static.
Assuming that you have ONE Activity responsible of all fragments:
1.Create an interface in your BookGrid:
public interface ActionHappened {
void onActionHappened();
}
2.Create an instance of your interface within your BookGrid class and trigger the method onActionHappened where you want it to be triggered. For instance, if you would like it to happen in your onDraw(), then do the following:
ActionHappened actionHappened;
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
actionHappened.onActionHappened();
}
3.Implement your interface within your activity
public class ActivityA extends AppCompatActivity implements BookGrid.ActionHappened {}
4.Within your implemented method, trigger the method:
#Override
public void onActionHappened() {
Fragment fragmentA = getSupportFragmentManager().findFragmentByTag(R.id.fragmentA);
Fragment fragmentB = getSupportFragmentManager().findFragmentByTag(R.id.fragmentB);
//Trigger that method from your activity to fragmentA or fragmentB
fragmentA.doWork();
fragmentB.doWork();
}
Wether you would like to pass data to fragmentA or fragmentB, doWork() method will do that for you. Make you create such a method in the corresponding fragment.

Fragment transactions: how to exchange data to former fragment

Okay, so I have a MainActivity.java which has a Fragment, called FavoritesFragment.java, at some point I call a MyListFragment.java where people are supposed to choose a list element.
After that I want to get their choice back in my FavoritesFragment.java.
So I implemented an interface for that. In MyListFragment, a method is called, that sets settings in FavoritesFragment:
From MyListFragment:
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Station s = (Station) l.getItemAtPosition(position);
updater.stationChosen(s, isFrom);
getFragmentManager().popBackStack();
}
Where updater is my interface.
So, everything works fine, my Content in FavoritesFragment is updated and everything. But after I call popBackStack() everything in FavoritesFragmentreset to former glory.
When I used to do everything with Activities there was this onActivityResult(...) callback from which you could exchange data. How can I do this with fragments? I don't want to use Activities for this minor case, and I want to have the list in it's own class, because I'm going to need it in other classes.
You need some place to keep track of favorites that is outside of the Fragments. One place is in the Activity that hosts the Fragments, which you can access via getActivity() and casting it to MainActivity or some interface that you define.
For example:
public interface FavoriteManager {
void onFavoriteAdded(Station s);
void onFavoriteRemoved(Station s);
List<Station> getFavorites();
etc.
}
public class MainActivity extends FragmentActivity implements FavoriteManager {
...
private List<Station> mFavorites;
...
#Override
public void onFavoriteAdded(Station s) {
mFavorites.add(s);
}
...
#Override
public List<Station> getFavorites() {
return mFavorites;
}
}
public class FavoritesFragment extends Fragment {
...
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Station s = (Station) l.getItemAtPosition(position);
FavoriteManager manager = (FavoriteManager) getActivity();
manager.onFavoriteAdded(s);
getFragmentManager().popBackStack();
}
...
}
public class MyListFragment extends Fragment {
...
#Override
public void onStart() {
List<Station> favorites = ((FavoriteManager) getActivity()).getFavorites();
populateUi(favorites);
}
...
}
Another option is to use a database and a ContentProvider, as shown here: http://developer.android.com/guide/topics/providers/content-providers.html
Perhaps it's better to think about the fragments existing simultaneously than one after the other (like activities would). You aren't calling a Fragment and getting a result, but calling the fragment and allowing it to change data.
I would think the easiest thing to use the Observer pattern to have your FavoritesFragment observe changes on the underlying data, and make sure that the update from MyListFragment calls notifyobservers(). Then when FavoritesFragment comes back to the front, it'll notice the change

How to invoke info-getting from an Android fragment to the MainActivity from within the MainActivity?

So I'm trying to wrap my head around Android Fragments. If I put the following code in my MainActivity:
public void getMessage(Object obj) {
Log.wtf("My object: ", obj.toString());
}
and the following code in my fragment:
((NewNotificationRule)getActivity()).getMessage("Yah wohooo!");
I get the "Yah wohooo!" into my MainActivity. The thing is that this pushes that string from my fragment to my Activity, where I want it to work the other way around. The fragment just defines a couple EditTexts, so upon hitting the submit-button defined in the xml called by the MainActivity, I want the MainActivity to pull the information defined in the EditTexts within the fragment so that it can submit it into the DB. So to conclude: I want to pull something (R.id.myEditText to be precise) from within my MainActivity instead of pushing it from within the fragment.
Is there any way that I can pull the contents of an EditText from a fragment into an Activity? All tips are welcome, since I'm totally lost here..
One fast option (not sure if this is safe or recommended tho) is creating a class in your project with attributes needed to store info and instancing an object of this class in the MainActivity. Then, reference it from the fragment and fill in it the data you need to save (e.g. within an attribute EditText1Data or something) whenever the text is changed or introduced into the fragment's EditText. Then just store into the DB the data contained in the object you filled with the Fragment info. Place some default values to the atttributes in the constructor of this called class to avoid null stuff problems. This can help you easily transfer Data in both directions Activity<-->Fragments , even tho this mightmean you must be very careful since you can get null pointer exceptions.
//This is Your DataClass used to transfer Data between Activity and Fragment.
public class DataClass {
public String EditText1Value;
public String EditText2Value;
public DataManager()
{
EditText1Value="Default Text";
EditText2Value="Default Text";
}
}
//This is the MainActivityClass
public class MainActivity extends Activity{
//instance of the DataClass to be passed to fragments with the method getDataClass
public DataClass dataClass = new DataClass();
//Main Activity code goes here...
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
}
//This method returns a reference to the DataClass Object
public DataClass getDataClass()
{
//Return this class instance object of the DataClass class
return (dataClass);
}
//Now this is the method to push data to DB, called whenever an activity button is pressed.
private boolean WriteToDB ()
{
//Suppose this receives a String
WritetoDB(dataClass.EditText1Value);
}
}
//And this is the Fragment that sends data through the DataClass Object
public class ExampleFragment extends Fragment {
//Used to reference MainActivityObject and store info
DataClass dataClass;
//Used to Reference Activity's EditTexts
private EditText editText1;
//TextWatcher used to detect the EditText Field Changes
private TextWatcher EditText1_Txtwtr;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View v = inflater.inflate(R.layout.whatever_layout, container, false);
editText1= (EditText)v.findViewById(R.id.idEditText1);
setHasOptionsMenu(true);
return v;
}
#Override
public void onResume ()
{
super.onResume();
//code...
//Get MainActivity's DataClass object reference.
dataClass= ((MainActivity)getActivity()).getDataClass();
//store info whenever you need to, not necessarily on each keystroke, and store it in the object, not in the DB
dataClass.EditText1Value = editText1.getText().toString();
// Also, to capture whenever a edittext changes, you can use a textwatcher.
EditText1_Txtwtr= new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3)
{}
#Override
public void afterTextChanged(Editable editable)
{}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3)
{
dataClass.EditText1Value = editText1.getText().toString();
}
}
//Asign TextWatcher to your Edit Text.
editText1.addTextChangedListener(EditText1_Txtwtr);
}
}

How to update TextView in parant Activity based on current ImageView in ViewPager

Im new to android dev and Im currently struggling with this annoying problem for days.
Tried google and many articles. None helped. Also tried IRC for some on line help but couldnt get a straight answer. Maybe you can direct me in the right direction...
My app is basically a ViewPager that holds a single Fragment class. Inside the Fragment class, there is a switch case block that determines based on position, which image to load from the resources folder. The thing is, I have a TextView View in my parent Activity that needs to get updated according to the current picture. Like an image title.
I used:
TextView tv = (TextView) getActivity().findViewById(R.id.titleTV);
in onCreateView() to get access to that TextView from within the Fragment. And added a:
tv.setText("Picture 1");
for instance, to the switch case block. This way, when a picture is shown, the text view gets updated.
The problem is, the method that retrieves a new Fragment with each slide, getItem(int position) in the parent Activity, gets called twice to load more than one Fragment to memory. This causes the current position to be one int ahead. Meaning, the switch case stands on case 0 for instance and it shows a specific picture, but the text gets updated from case 1.
I cant get it to work properly because of that.
What am I doing wrong??
Thank you
If you are able to determine text based on current fragment position only you can use ViewPager.OnPageChangeListener. In you Activity implement that interface and use it by calling mViewPager.setOnPageChangeListener(SomeOnPageChangeListener). Determine what text put into TextView with OnPageChangeListener.onPageSelected() method.
Unfortunately there is a bug in SDK and onPageSelected() is not called for page at position 0 when it is shown for the first time. You can find workaround under above link, use ViewPager.setCurrentPage(1) or just set text for that position manually in onCreate() method.
Example code:
//activity onCreate()
protected void onCreate(Bundle savedInstanceState) {
...
mTextView = (TextView) findViewById(R.id.text_view);
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
String someText = determineText(position)
mTextView.setText(someText);
});
//because onPageSelected will not be called for page 0 first time
String someText = determineText(0)
mTextView.setText(someText);
}
private String determineText(int position){ //static?
//switch?
}
Ok I'll give a piece of code, you can use that, not just for a String but for any data at all that you want to send to the Activity from the Fragment, or even from other type of classes between each other.
That's the type of code is also used on .'setOnSomethingHappenListener(listener);'
public interface TitleChangeListener{
public void onTitleChanged(String title);
}
then on your activity you will
public class MyActivity extends Activity implements TitleChangeListener{
#Override
onTitleChanged(String title){
// set here your value
}
}
then on your fragment
public void MyFragment extends Fragment{
private TitleChangeListener listener;
#Override
public void onAttach(Activity activity){
if(activity instanceof TitleChangeListener)
listener = (TitleChangeListener)activity;
else
// throw some error ???
}
#Override
public void onResume(){
listener.onTitleChanged("my title");
}
}
and remember I typed all of that by heart, there is bound to have some small mistakes.
But just follow the idea and you'll be fine.

Categories

Resources