I'm creating application which has FragmentPagerAdapter with two pages.
The class for FragmentPagerAdapter looks like this
public static class AppSectionsPagerAdapter extends FragmentPagerAdapter {
public AppSectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment;
switch (i) {
case 0: fragment = new CurrentRateFragment(); break;
case 1: fragment = new HistoryFragment(); break;
default: fragment = new CurrentRateFragment(); break;
}
return fragment;
}
#Override
public int getCount() {
return 2;
}
}
I want that some changes on first page (for example change Spinner selected item) caused changes on second page.
As I've read about Fragment communication (https://developer.android.com/training/basics/fragments/communicating.html) and understand that fragments can communicate only through Activity.
For this case I've created public interface in my first page class fragment
public interface CurrencyListener {
public void onCurrencyChanged();
}
And implement it in my Activity.
Now I can call void onCurrencyChanged in my Activity from my first page fragment.
But the problem is:
How to recreate second page fragment in my FragmentPagerAdapter?
Fragment creation operation is heavy.
1. If you recreate fragment the all the views will be created and previous Fragment needs to be garbage collected. Therefore there will extra memory.
Therefore rather than recreating the FirstPageFragment just refresh the data based on the callback received on your currencyChanged() method.
In this case, fragment will be created once and data will be updated every time the currencyChanged() method is called.
HistoryFragment Code(Fragment to be refreshed)
public class HistoryFragment extends Fragment implements MyActivity.IUpdateData{
/**
* Other method goes here
*/
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//
if(getActivity() instanceof /**Your Activity**/){
((/**Your Activity**/)getActivity()).setOnUpdateListener(this);
}
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void updateData(Object object) {
//Refresh Data
}
}
Activity (Which will refresh data)
public class MyActivity extends Activity {
/**
* Other method goes here
*/
private IUpdateData dataUpdateListener;
public void setOnUpdateListener(IUpdateData listener){
dataUpdateListener = listener;
}
public void onCurrencyChanged(){
if(dataUpdateListener!=null){
dataUpdateListener.updateData(/**Update Data**/);
}
}
public interface IUpdateData{
void updateData(Object o);
}
}
Related
I have an Activity with three tabs and three Fragments. First Fragment shows the list of song titles and the second tab displays the selected song. The details of the songs is coming from a database.
I am implementing SearchView feature so that whatever search text user enters, only those songs should be displayed in the index.
This is exactly like the way phone book work in our devices.
I'm not able to understand how to refresh the first Fragment when the search query changes.
I'm basically looking for the method that I can call to refresh the first Fragment.
Got my answer here Android refresh a fragment list from its parent activity as suggested by #Prem. Works perfectly fine for me.
Its is achievable by making an interface
MainActivity.java
public class MainActivity extends Activity {
public FragmentRefreshListener getFragmentRefreshListener() {
return fragmentRefreshListener;
}
public void setFragmentRefreshListener(FragmentRefreshListener fragmentRefreshListener) {
this.fragmentRefreshListener = fragmentRefreshListener;
}
private FragmentRefreshListener fragmentRefreshListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b = (Button)findViewById(R.id.btnRefreshFragment);
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(getFragmentRefreshListener()!=null){
getFragmentRefreshListener().onRefresh();
}
}
});
}
public interface FragmentRefreshListener{
void onRefresh();
}}
MyFragment.java
public class MyFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = null; // some view
/// Your Code
((MainActivity)getActivity()).setFragmentRefreshListener(new MainActivity.FragmentRefreshListener() {
#Override
public void onRefresh() {
// Refresh Your Fragment
}
});
return v;
}}
Below is the one way, you can easily do it. You just have to save the fragment instance. That's all.
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
private SparseArray<Fragment> array = new SparseArray<>();
ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
array.setValueAt(position, myFragment);
return myFragment;
}
#Override
public int getCount() {
return 3;
}
public SparseArray<Fragment> getFragmentArray() {
return array;
}
}
Then from your activity, you can get all viewpager fragment easily from adapter instance.
MyFragment fragment = (MyFragment) adapter.getFragmentArray().get(0); // get first fragment and casting it to your origin fragment
Then create a method on that fragment. Call like below:
fragment.myFragmentMethod(String myData){}
Have a nice day!
I am trying to call the functions frag.updateButtons() etc from within GameScreen (Main Activity) as ten times a second, the game cycles through these things and updates the variables accordingly.
The code is as it currently is. The frag.*** functions work when it is set up as a fragment, but now that I am wanting to use it as viewPager, it keeps having the following error:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.support.v4.app.FragmentActivity.findViewById(int)' on a null object reference
I am fairly new to programming and Android code, and clearly don't entirely understand fragments and viewPager etc, so any help here would be very much appreciated.
I have not included the whole code, just the code that I think is relevant to the problem. If you need more code or info, please ask me and I'll get you what you need.
Thank you
public class GameScreen extends FragmentActivity implements GameScreenFragment.FragInterface{
ViewPager mViewPager;
GameScreenFragment frag;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game_screen);
mViewPager = (ViewPager) findViewById(R.id.bottomFragment);
/** set the adapter for ViewPager */
mViewPager.setAdapter(new SamplePagerAdapter(getSupportFragmentManager()));
startRepeatingTask();
//the code here calls the mainLoop() function 10 times a second
}
}
//for the fragment functions
public void displayCompany(String companyName){}
public void displayCompanyIncome(){}
public void checkTeamImage(){}
public void getCostOfEmployees(){}
public void updateButtons(){}
public void mainLoop(){
//display company name and company income in fragment
frag = (GameScreenFragment) getSupportFragmentManager().findFragmentById(R.id.bottomFragment);
frag.updateButtons();
frag.displayCompanyIncome();
frag.getCostOfEmployees();
frag.displayCompany(Global.companyName[Global.companyNumber]);
//updates the picture for that company
frag.checkTeamImage();
}
public class SamplePagerAdapter extends FragmentPagerAdapter {
public SamplePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
/** Show a Fragment based on the position of the current screen */
Global.companyNumber = position;
return new GameScreenFragment();
}
#Override
public int getCount() {
// Show 2 total pages.
return 2;
}
}
}
and then in the fragment whose ID in the .xml is bottomFragment
public class GameScreenFragment extends Fragment {
FragInterface fragStuff;
public interface FragInterface {
void displayCompany(String companyName);
void displayCompanyIncome();
void checkTeamImage();
void getCostOfEmployees();
void displayCompanyName();
void updateButtons();
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState){
View view = inflater.inflate(R.layout.game_screen_fragment, container, false);
//other code here
return view;
}
}
Main goal is to update Fragment info mainly from its own class.
Main activity:
public class MainActivity extends AppCompatActivity {
final Handler GUIHandler = new Handler();
final Runnable r = new Runnable()
{
public void run()
{
updateFragments();
GUIHandler.postDelayed(this, 1000);
}
};
#Override
protected void onPause() {
super.onPause();
GUIHandler.removeCallbacks(r);
}
#Override
protected void onResume() {
super.onResume();
GUIHandler.postDelayed(r, 600);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mViewPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
mViewPager.setAdapter(mPagerAdapter);
...
}
private void updateFragments() {
mPagerAdapter.updateFragments();
}
PagerAdapter:
public class PagerAdapter extends FragmentStatePagerAdapter {
int mNumOfTabs;
private Observable mObservers = new FragmentObserver();
public PagerAdapter(FragmentManager fm, int NumOfTabs) {
super(fm);
this.mNumOfTabs = NumOfTabs;
}
#Override
public Fragment getItem(int position) {
mObservers.deleteObservers(); // Clear existing observers.
switch (position) {
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
case 1:
FragmentMemo tab2 = new FragmentMemo();
return tab2;
case 2:
FragmentHardware tab3 = new FragmentHardware();
return tab3;
default:
return null;
}
}
public void updateFragments() {
mObservers.notifyObservers();
}
}
FragmentObserver
public class FragmentObserver extends Observable {
#Override
public void notifyObservers() {
setChanged(); // Set the changed flag to true, otherwise observers won't be notified.
super.notifyObservers();
Log.d("Observer", "Sending notification");
}
}
FragmentWeather:
public class FragmentWeather extends Fragment implements Observer {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
return layout;
}
public void setTemperatures(){
Log.d("Android", "setTemperatures is called");
}
#Override
public void update(Observable observable, Object data) {
setTemperatures();
}
}
Problem now is, that PagerAdapter::getItem() doesnt get called when Fragments are created at the start of application. That means WeatherFragment dont get associated with mObservers. If I swipe to the 3rd view and then swipe back, everything is working properly. How to restructurize this to make it working?
this line:
mObservers.deleteObservers(); // Clear existing observers.
is removing all the observers, but the method getItem gets called several times, that means only the last time it calls anything stays there. REMOVE this line.
Also, the following code is a very bad pattern and it will go wrong on several occasions:
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
that's because fragments get re-created by the system when necessary, so setActivity is pointless, so as is addObserver. The moment the system needs to destroy/recreate the fragments, you'll have a memory leak of those old fragments, the old activity, and the new ones won't have the activity and won't be on the observers.
The best situation here is to rely on the natural callbacks from the fragments. An example follows (ps.: that was typed by heart, I'm sure there might be some mistakes, but you'll get the idea)
public interface ObservableGetter{
public Observable getObservable();
}
public void MyFragment extends Fragment implements Observer {
#Override onAttach(Activity activity){
super.onAtttach(activity);
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
addObserver(this);
}
}
#Overrude onDetach(){
Activity activity = getActivity();
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
removeObserver(this);
}
super.onDetach();
}
}
then you can just make the activity implements ObservableGetter and have the Observable on it.
Then your adapter code will be just:
case 0:
return new FragmentWeather();
all the rest of the logic uses the regular callbacks.
I hope it helps.
I'm trying to update an object from a fragment contained within a swipe view. The code I have is taken directly from the Android documentation. What I want to do is pass an object from the main CollectionDemoActivity down into the DemoObjectFragment fragment, update it using a button in that fragment and then pass it back up to the main activity. What's the best way to accomplish this?
I've tried passing the object in a bundle as a serialisable through the DemoCollectionPagerAdapter and then again down to the fragment but this seems really cumbersome. I've also tried declaring the object in the main activity and just referencing it in the fragment class but I get complaints that it can't have a non-static reference in a static context.
public class CollectionDemoActivity extends FragmentActivity {
// When requested, this adapter returns a DemoObjectFragment,
// representing an object in the collection.
DemoCollectionPagerAdapter mDemoCollectionPagerAdapter;
ViewPager mViewPager;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_collection_demo);
// ViewPager and its adapters use support library
// fragments, so use getSupportFragmentManager.
mDemoCollectionPagerAdapter =
new DemoCollectionPagerAdapter(
getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mDemoCollectionPagerAdapter);
}
}
// Since this is an object collection, use a FragmentStatePagerAdapter,
// and NOT a FragmentPagerAdapter.
public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
public DemoCollectionPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new DemoObjectFragment();
Bundle args = new Bundle();
// Our object is just an integer :-P
args.putInt(DemoObjectFragment.ARG_OBJECT, i + 1);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return 100;
}
#Override
public CharSequence getPageTitle(int position) {
return "OBJECT " + (position + 1);
}
}
// Instances of this class are fragments representing a single
// object in our collection.
public static class DemoObjectFragment extends Fragment {
public static final String ARG_OBJECT = "object";
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
// The last two arguments ensure LayoutParams are inflated
// properly.
View rootView = inflater.inflate(
R.layout.fragment_collection_object, container, false);
Bundle args = getArguments();
((TextView) rootView.findViewById(android.R.id.text1)).setText(
Integer.toString(args.getInt(ARG_OBJECT)));
return rootView;
}
}
So after a lot of searching and reading I found a nice solution that works for me. For those interested I created an interface in the fragment class that is implemented in the Main activity. The methods were kicked off through a button press in the fragment class. This way I was able to pass variables up to the main class without ever needing to pass the entire object down to the fragment.
So my classes were mostly the same with these bits added:
And the fragment class which contains the interface. The onAttach() method needs to be called which gets a reference to the activity that the fragment will be attached to. This activity reference is binded to an instance of the interface in the fragment.
public class DemoObjectFragment extends Fragment {
....
//Creating the interface
public interface ButtonListener {
//This method will be called in the main activity. Whatever is passed in as the parameter can be used by the main activity
public void ButtonPressed(int myInt);
}
//Getting an instance of the interface
ButtonListener updateListener;
//Getting a reference to the main activity when the fragment is attached to it.
//The activity reference is bound to the instance of the interface.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Ensures the activity implements the callback interface
try {
updateListener = (DayUpdateButtonListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString());
}
}
....
//On the button click call the method through the activity reference from the onAttach() method
//Creating an int object to pass into the method.
int myNewInt = 5;
myButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
updateListener.ButtonPressed(myNewInt);
}
});
}
Finally in the main activity simply implement the interface and add the method from it.
public class CollectionDemoActivity extends FragmentActivity implements DemoObjectFragment.ButtonListener {
....
#Override
public void ButtonPressed(int myInt) {
//Update the object with myInt
}
}
I'm currently trying to work with fragment, but I'm stuck with an issue I can't solve.
I have one activity, which holds 4 different fragment. From this activity, I launch an ASyncTask which goes to the web and get different data I need, and then will send it to the fragments.
But, when my app gets killed and opened again, or when I change the orientation, my fragments are apparently recreated and my custom FragmentAdapter doesn't hold the good reference to the fragment.
Here is the code of my main activity.
public class MainActivity extends FragmentActivity {
MyPagerAdapter fgsAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//...
FragmentManager fm = super.getSupportFragmentManager();
fgsAdapter = new MyPagerAdapter(fm,this);
ViewPager myPager = (ViewPager) findViewById(R.id.home_pannels_pager);
myPager.setAdapter(fgsAdapter);
myPager.setCurrentItem(0);
}
#Override
protected void onResume() {
super.onResume();
ATaskGetUser task = new ATaskGetUser(callback, (ProgressBar) findViewById(R.id.PB_AsyncTask));
task.execute();
}
//What's called by the ASyncTask onPostExecute()
private void notifyDataChanged() {
fgsAdapter.notifyFragments(user.getItems());
}
private class MyPagerAdapter extends FragmentPagerAdapter {
private List<CardFragment> fragments = new ArrayList<CardFragment>();
private Context c;
public MyPagerAdapter(FragmentManager fm, Context c) {
super(fm);
CardFragment h = new HabitFragment();
CardFragment d = new DailyFragment();
CardFragment t = new ToDoFragment();
CardFragment r = new RewardFragment();
fragments.add(h);
fragments.add(d);
fragments.add(t);
fragments.add(r);
}
public int getCount() {
return fragments.size();
}
#Override
public CardFragment getItem(int position) {
Log.v("MainActivity_fgsmanager", "getItem()");
CardFragment f = (CardFragment) this.fragments.get(position);
return f;
}
public void notifyFragments(List<HabitItem> items) {
for(OnTasksChanged f : fragments) {
f.onChange(items);
}
}
}
}
So, what I want to be able to do, is to be able to call the onChange (an interface implemented by my four fragments), in my notifyDataChanged function. Is this possible, are am I thinking the wrong way?
I got the same problems once with Fragments, I was losing the current fragment after every screen rotation.
I simply solved it by adding one line in the Fragment class (not in the parent FragmentActivity class):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.rate_fragment, container,false);
setRetainInstance(true);//Added not to lose the fragment instance during screen rotations
(...)
For your case where your app gets killed and opened again, I am not sure it will work though.