I'm doing some test after change device configuration (change language, orientation, etc), and i notice that after this, the method "notifyDataSetChanged()" is not working.
The action example:
I'm calling updateList() everytime i do an action like delete, save, etc. The user click a delete button, a DialogFragment is shown, "Are you sure you want to delete?", when i change the orientation, or the language, or any configuration of the device and then click "yes" on the Dialog, the data is removed, but the list doesn't update. I need to quit the activity, then go back to see the alteration.
BookAdapter:
public void updateList(ArrayList<Book> books) {
bookList = books;
notifyDataSetChanged();
}
What can i do to make it works after the configuration change?
Edit:
BookAdapter Constructor:
public BookAdapter(Context c, ArrayList<Book> books) {
context = c;
bookList = books
bookDAO = BookDAO.getInstance(context);
}
BookFragment:
public class BookFragment extends Fragment {
private BookDAO bookDAO;
private BookAdapter bookAdapter;
private ListView listBook;
private View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bookDAO = bookDAO.getInstance(getActivity());
view = inflater.inflate(R.layout.book_tab, container, false);
ArrayList<Book> listBook = null;
try {
llistBook = bookDAO.getAll();
} catch (Exception e) {
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
return view;
}
bookAdapter = new BookAdapter(getActivity(), listBook);
listBook = (ListView)view.findViewById(R.id.listBook);
listBook.setAdapter(bookAdapter);
return view;
}
}
You can try implementing BookAdapter as a Singleton to confirm that you are not calling updateList(..) from a stale reference.
Changes that you will need to make:
// I am assuming that you are using a BaseAdapter because
// BookAdapter's constructor that you provided in the code above
// does not contain a call to super(....)
public class BookAdapter extends BaseAdapter {
private static BookAdapter mAdapter;
private Context context;
private static ArrayList<Book> bookList;
private BookDAO bookDAO;
// To keep at most one instance of BookAdapter
public static BookAdapter getInstance(Context con, ArrayList<Book> books) {
// If an instance exists, return it
if (mAdapter != null) {
bookList = books;
return mAdapter;
}
// Else, craete a new instance
mAdapter = new MyAdapter(con, books);
return mAdapter;
}
// BookAdapter's only constructor is declared as private to restrict access
private BookAdapter(Context con, ArrayList<Book> books) {
context = con;
bookList = books;
bookDAO = BookDAO.getInstance(context);
}
public void updateList(ArrayList<Book> books) {
bookList = books;
notifyDataSetChanged();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Retrieve object
Book bookItem = bookList.get(position);
....
....
}
}
This is how the Fragment's onCreateView will change:
bookAdapter = BookAdapter.getInstance(getActivity(), listBook);
Code that will be executed when the user presses yes to Are you sure you want to delete?:
// Remove entry from bookDAO
// Remove entry from listBook
// OR update listBook:
try {
listBook = bookDAO.getAll();
} catch (Exception e) {
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
}
// Assertion: "listBook" does not contain the
// item that was just deleted from "bookDAO"
// Update ListView's contents
bookAdapter.updateList(listBook);
The problem occurs because every time your rotate, change language, etc... the activity is recreated and your fragments are also recreated (new instance), so the notififyDataSetChanged is actually notifying the old instances of your fragments.
A solution for that would be. Make your fragments static. Then you create some refresh method for your fragments, and called it when you press yes for your dialog.
In your activity you should have something like this.
private static BookFragment bookFragment;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
if (fragment1 == null) {
bookFragment = new BookFragment();
}
...
}
Create some interface like:
public interface Refreshable {
public void refresh();
}
Then implement this interface all your fragments.
In the method that is called when the dialog is answered positively you should call
...
fragment.refresh();
...
Inside the refresh method of you fragment you can call its adapter method updateList(...)
Might not be the prettier solution, but it works....
Why this happen... Google's Android Dev Team might know.
I am using an ArrayList of Strings in my GridViewAdapter which extends BaseAdapter.
so in case i changed the data concerning the List, i cann notifyDataSetChanged() on the Adapter. I dont really see the point why you cant call it?
So what i would do is ovverride this method, and just call notifyDataSetChanged()
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
adapter.notifyDataSetChanged();
}
Besides that, does your Book data chnage based on your configuration/orientation?
Try using the same list for the whole adapter lifecycle, and change only its content:
public class BookAdapter extends ArrayAdapter<Book> {
private final booklist;
// ..
public BookAdapter(Context c, ArrayList<Book> books) {
super(c, R.layout.yourlayout, books);
context = c;
bookList = books
bookDAO = BookDAO.getInstance(context);
}
public void updateList(ArrayList<Book> books) {
bookList.clear();
boolList.addAll(books);
notifyDataSetChanged();
}
// ..
}
Why are you keeping your View as a data member?
When keeping a view across configuration changes, you hold a reference to the previous instance, before the configuration change was happening. Since it's the view holding your list - it may miss data updates.
Try to remove this field, and the use of it, and see if the list now updates.
I think the problem comes from the fact that any configuration change such as the orientation will restart your current Activity.
Because of this, I guess some parts of your code are still referencing the previous activity that does not exist anymore and the notifyDataSetChanged is not working anymore.
There are 2 things you can quickly try:
Add this line in the manifest file for your activity: android:configChanges="orientation|locale". This change means to the system that you will handle yourself the changes to do during orientation or language changes. Therefore, the app will not recreate the activity by itself anymore (so the activity should work the same).
The other trick can be to add this line at the beginning of the function onCreateView:
setRetainInstance(true);.
This line will retain the fragment state during configuration changes as the documentation explains:
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
Just be informed that as explained the Fragment lifecycle will change a little.
3) Last option could be to retain the activity state using onSaveInstanceState and onRestoreInstanceState as explained in details in this answer: https://stackoverflow.com/a/151940/2206688
Related
Let's assume that we have Activity/Fragment which contains a RecyclerView. Furthermore, it sets an Adapter. For the sake of the example, let's say the Adapter has to have access to Fragment in order to call a method which displays a Snackbar. Moreover, Let's say there are a couple of items in the adapter. I want to delete one and remove it from the database. Therefore I should call ViewModel's methods. I've made a research but I couldn't find any information if referencing a fragment into the Adapter is good or not.
Could you help me and explain? Also for the ViewModel I've found some ideas here.
But what are the best practices?
good Adapter Classes should be STATIC helping developers to keep it separated from Activity/Fragment part
don't save Activity/Fragment reference inside Adapters
ViewModels should belongs to Activities or Fragments
Adapters should execute Activity/Fragment's actions via Callbacks/Listeners or LiveData
Pseudo-code:
public class MainActivity extends Activity {
private interface Listener {
void OnRemoved(#NonNull xxx removedItem);
}
private static final class MyAdapter extends ArrayAdapter<xxx> {
private final Listener mListener;
private MyAdapter(#NonNull final Listener listener) {
super(...);
this.mListener = listener;
}
#Override
public void remove(xxx item) {
super.remove(xxx); //<-- this removes item from Adapter
this.mListener.OnRemoved(item); //<-- this triggers Activity's code
}
}
public void onCreate(...) {
...
new MyAdapter(new Listener() {
#Override
public void OnRemoved(#NonNull final xxx removedItem) {
Snakbar.makeText(....).show();
}
});
}
}
I understand there are a lot of information about it out there, but I haven't found one that matches my case yet.
I have a recycleview on a fragment that is always open, so the fragment basically never re-creates itself.
This is my code to load the adapter.
reLoad(); //method shown below
mRecycler.setAdapter(new SolicitationAdapter(myRealm.where(SolicitationDatabase.class).findAllAsync()));
And this is the logic I came up with:
public void reLoad() {
if (!myRealm.where(SolicitationDatabase.class).findAll().isEmpty()) {
mNothingHere.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
} else {
mNothingHere.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
It works great the first time the user opens the app.
The trouble starts when the user creates a record, since the fragment doesn't re-create itself it never reloads.
The reason I haven't been able to reload after user adds something is because the method to add a new record is on a singleton being called from a different activity. Which means when I try to do it I get a nullpointerexception when declaring the the recycleview and the textview.
Edit - What I tried (reloading views from another place)
I have a class called PostHelper, this class is in charge of posting a new record.
This is the constructor:
public PostHelper(Context context, Activity activity) {
this.mContext = context;
this.mActivity = activity; //I call this in order to use "findViewById"
This is where the post happens:
public String addSolicitation(File _file, boolean fromQueue) {
//loading view
TextView nothingHere = (TextView) mActivity.findViewById(R.id.nothing_here);
RecyclerView recycler = (RecyclerView) mActivity.findViewById(R.id.recycler);
...so on until after the post:
SolicitationAdapter n = new SolicitationAdapter(myRealm.where(SolicitationDatabase.class).findAll());
n.notifyDataSetChanged();
nothingHere.setVisibility(View.GONE);
recycler.setVisibility(View.VISIBLE);
And this is the stacktrace:
06-01 21:43:37.511 9122-9122/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.ga.realm3, PID: 9122
io.reactivex.exceptions.OnErrorNotImplementedException: Attempt to invoke virtual method 'void android.widget.TextView.setVisibility(int)' on a null object reference
Edit 2 - I load PostHelper class using the following:
mPostHelper = new PostHelper(this, PostSolicitationActivity.this);
You're supposed to make sure that SolicitationAdapter is a RealmRecyclerViewAdapter, like so:
public class SolicitationAdapter extends RealmRecyclerViewAdapter<SolicitationDatabase, SolicitationViewHolder> {
public SolicitationAdapter(OrderedRealmCollection<SolicitationDatabase> results) {
super(results, true);
}
...
}
And then what you need to do is that you put the RealmResults as a field reference in your Activity:
public class PostSoliticiationActivity extends AppCompatActivity {
RealmResults<Solicitation> results;
Realm realm;
RealmChangeListener<RealmResults<Solicitiation> realmChangeListener = (results) -> {
if(results.isLoaded() && results.isValid()) {
if(results.isEmpty()) {
mNothingHere.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
} else {
mNothingHere.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
}
SolicitationAdapter adapter;
#Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.soliticiation_activity);
// bind views
realm = Realm.getDefaultInstance();
results = realm.where(SolicitationDatabase.class).findAllSortedAsync("id");
// .sort("id").findAllAsync(); in 4.3.0+
results.addChangeListener(realmChangeListener);
adapter = new SoliticiationAdapter(results);
mRecycler.setAdapter(adapter);
// layout manager as well
}
#Override
public void onDestroy() {
results.removeChangeListener(realmChangeListener);
realm.close();
super.onDestroy();
}
}
So things you don't need:
1.) reLoad() method
2.) onPostAdded callback
3.) PostActionListener
As long as you just add the SoliticiationDatabase to the Realm in a transaction, it'll all work without manually syncing ui.
If I understand correctly, you want to be notified in another view when an action happens elsewhere.
The way to do that is usually interfaces.
public class PostHelper {
// Define these
public interface PostActionListener {
void onPostAdded();
}
private PostActionListener postListener;
public void setPostActionListener(PostActionListener listener) throws ClassCastException {
this.postListener = (PostActionListener) context;
}
public PostHelper(Context context) {
this.mContext = context;
// Cast the passed context as a listener
if (mContext instanceof PostActionListener) {
this.postListener = (PostActionListener) mContext;
}
}
public String addSolicitation(File _file, boolean fromQueue) {
// Do something
// Callback to the UI to update
if (this.postListener != null) {
this.postListener.onPostAdded();
}
}
Then, in your initial Activity
public class YourActivity extends AppCompatActivity
implements PostHandler.PostActionListener {
// ... fields
#Override
public void onPostAdded() {
reLoad();
}
public void reLoad() {
boolean emptyList = myRealm.where(SolicitationDatabase.class).findAll().isEmpty();
mNothingHere.setVisibility(emptyList ? View.VISIBLE : View.GONE);
mRecycler.setVisibility(emptyList ? View.GONE : View.VISIBLE);
}
#Override
public void onCreate(Bundle b) {
...
mPostHelper = new PostHelper(this);
}
However, since you are using Realm, and there really is no data that you need to "return" here, then you can simply let the Android Activity lifecycle refresh the data for you.
So I have an Activity. The Activity hosts a ViewPager with tabs, each tab holding a Fragment in it. The Fragments themselves have a RecyclerView each. I need to communicate changes from the RecyclerView's adapter to the activity.
Currently, I am using the listener pattern and communicating using interface between each of the components. i.e I have an interface between the RecyclerView's adapter and the Fragment holding it. Then an interface from the Fragment to the ViewPager's FragmentStatePagerAdapter which is creating all the Fragments. And 1 more interface between the ViewPager's adapter and the Activity hosting the ViewPager. I feel that there are too many interfaces for all the components because of how they are structured.
Currently I am not facing issues as such but I think the listener pattern is acting like an anti-pattern due to all the nested components. Instead of creating independent components I think the hierarchy will make it difficult for making code changes in future.
Am I doing it correctly or is there a better way to do it? Is this a case where I should use an Event Bus or Observer Pattern (If yes can you point me to some examples where someone overcame a similar problems using it)?
NOTE : If it matters, I need it to maintain a global object in the activity, something like a shopping cart where I can add or remove items and these items are present in RecyclerView's adapter from where I can add it to the cart and also increment or decrement the count for a particular item. The ViewPager and Tabs help segregate these items in various categories.
Edit 1 : Some code trying out #LucaNicoletti's approach -
I have skipped one level that is the level with the ViewPager's FragmentStatePagerAdapter. I guess that should not matter and stripped of some other code to keep it small.
MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener, FoodAdapter.OnFoodItemCountChangeListener {
#Override
public void onFoodItemDecreased(FoodItemModel foodItemModel, int count) {
Log.d("Test", "Dec");
}
#Override
public void onFoodItemIncreased(FoodItemModel foodItemModel, int count) {
Log.d("Test", "Inc");
}
// Other methods here
}
Fragment hosting the Adapter:
public class FoodCategoryListFragment extends Fragment implements FoodAdapter.OnFoodItemCountChangeListener {
// Other boring variables like recyclerview and layout managers
FoodAdapter foodAdapter;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Other boring intializations for recyclerview and stuff
// I set the click listener here directly on the adapter instance
// I don't have this adapter instance in my activity
foodAdapter.setOnFoodItemClickListener(this);
rvFoodList.setAdapter(foodAdapter);
}
}
The adapter class at the lowest level:
public class FoodAdapter extends RecyclerView.Adapter<FoodAdapter.FoodViewHolder> {
private OnFoodItemCountChangeListener onFoodItemCountChangeListener;
private List<FoodItemModel> foodItems;
// The interface
public interface OnFoodItemCountChangeListener {
void onFoodItemIncreased(FoodItemModel foodItemModel, int count);
void onFoodItemDecreased(FoodItemModel foodItemModel, int count);
}
// This is called from the fragment since I don't have the adapter instance
// in my activty
public void setOnFoodItemClickListener(OnFoodItemCountChangeListener onFoodItemCountChangeListener) {
this.onFoodItemCountChangeListener = onFoodItemCountChangeListener;
}
// Other boring adapter stuff here
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bMinus:
onFoodItemCountChangeListener.onFoodItemDecreased(foodItems.get(getAdapterPosition()),
Integer.parseInt(etCounter.getText().toString()));
}
break;
case R.id.bPlus:
onFoodItemCountChangeListener.onFoodItemIncreased(foodItems.get(getAdapterPosition()),
Integer.parseInt(etCounter.getText().toString()));
}
break;
}
}
}
my comments were:
what you should/could do it's to have a global data repo which holds the shopping cart and listeners associated with changes to it. Like a singleton, like ShoppingCart.getInstance().addListener(this); and ShoppingCart.getInstance().addItem(new Item(id));
and
Yes. That's what I'm suggesting. Do not forget that this Singleton can never ever holds Context or Activity because u don't want to leak memory, so always call removeListener. On my opinion it would reduce dependency as all your view controllers only interact with the data model
and I'll add some code to exemplify as a proper answer.
Below is a very crude, typed by heart code, but it should give an idea. All the UI elements are only tied to the data, and not to each other.
Similar stuff could be implemented with libraries that provide observable pattern out of the box for data-only objects.
public class ShoppingCart {
private ShoppingCart single;
private static void init(){
.. init single if not null
}
private List<Item> items = new ArrayList<>();
public int numberOfItems;
public long totalPrice;
private static void addItem(Item item){
init()
single.items.add(item);
single.numberOfItems++;
single.totalPrice+=item.price;
dispatchChange();
}
private static void removeItem(Item item){
init();
single.numberOfItems--;
single.totalPrice-=item.price;
dispatchChange();
single.items.remove(item);
}
private void dispatchChange(){
// TODO: write real loop here
for(single.listeners) listener.onCartChanged(single.cart);
}
public interface Listener {
void onCartChanged(ShoppingCart cart);
}
private List<Listener> listeners = new ArrayList<>();
// TODO: addListener and removeListener code
public static class Item {
String id;
String name;
long price;
}
}
To communicate between components (Activity, Fragment) you have to use an event bus.
In android, you could choose between:
RxJava
Otto
Green Robot EventBus
A blog to explain this.
I was looking over this answer and it seemed to only deal with a single textview.
Basically, I have an Android application with n fragments, each of which has a textview that is populated from a remote call to a database. Each time the fragment is selected, that remote call will fire and the textview should be repopulated.
Currently, I am using a central AsyncTask to accomplish this, however I am starting to wonder if it is the correct way to go about doing so (some textviews take too long to update for small amounts of data, some don't get updated at all, etc.).
Here is the code from my RetrieveData class. Essentially, it figures out which textview is to be updated, and then populates that textview.
public class RetrieveData extends AsyncTask<String, String, String[]> {
private int txtViewID = -1;
private Activity mainActivity;
public RetrieveData(Activity a) { mainActivity = a; }
protected String[] doInBackground(String... urls) {
String[] data;
// call web script to return JSON data
...
// figure out which fragment called which script
if (urls[0] == "get_A.php") {
data = parseJSONdata(); // parse out the JSON
txtViewID = R.id.txtViewA; // find INT-based ID
} else if (urls[0] == "get_B.php") {
data = parseOtherJSONdata(); // different type of call
txtViewID = R.id.txtViewB;
} else ... {
...
}
} catch (Exception e) {
System.out.println("Error: " + e.toString());
}
return data;
}
#Override
protected void onPostExecute(String[] op) {
if (txtViewID != -1) { // call was made
TextView tv = (TextView)mainActivity.findViewById(txtViewID);
tv.setText(op[0]);
}
and here is how I call this from a Fragment:
public class MainFragment extends Fragment {
Activity mainActivity;
public MainFragment(Activity a) { mainActivity = a; }
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v =inflater.inflate(R.layout.main_tab,container,false);
new RetrieveData(mainActivity).execute("get_A.php","1");
return v;
}
}
To me, its very kludgy and probably belies my newness to Android, so any suggestions for improvement are heartily appreciated.
You can do a couple of things to improve the robustness and performance and fix some issues which will creep in later:
Don't use findViewById() outside of init/setup type methods. It is an expensive call as it has to "search" your hierarchy for the ID you are requesting.
Don't use an overloaded constructor for your Fragment which takes the Activity. The Fragment default constructor should be empty. This allows the system to properly re-create your Fragment when configuration changes (screen rotates.) The Fragment will receive its attached Activity at the correct time when its onAttach() method is called, so there is no need to do this.
You shouldn't need the Activity at all for what you're trying to do. Instead, have your Fragment get the correct TextView from your layout in its onCreateView(). What you do from there is really up to you:
Pass the TextView instance to your RetrieveData class constructor as the one to be updated. This eliminates the hard coded IDs in your RetrieveData class, which gets rid of some explicit coupling and is a better approach. This is still very tightly coupled, though, since it depends on having a specific View so still not a great option IMHO.
Have the RetrieveData class define an inner Callback interface and have the Fragment implement it. The constructor for RetrieveData can then take an instance of the Callback interface (e.g. your Fragment instance) and when its onPostExecute() runs it just calls back the Fragment with the appropriate data. Now it is up to your Fragment implementation to make the right decision on what UI element it is hosting to update with the data. It may be a TextView now, but in the future you could make it something else, etc. Now you have decoupled the class from all explicit UI ties and put the responsibility on the thing hosting the UI elements: the Fragment.
Here's a brief example of the 2nd bullet:
public RetrieveData extends AsyncTask<String, String, String[]> {
// Define the interface used to provide results
public interface Callback {
public void onDataLoaded(String[] result);
}
private Callback mCb;
public RetrieveData(Callback cb) {
mCb = cb;
}
...
#Override
public void onPostExecute(String[] result) {
mCb.onDataLoaded(result);
}
}
public MyFragment extends Fragment implements RetrieveData.Callback {
TextView mResult;
RetrieveData mAsyncRetriever;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.main_tab,container,false);
// Get the TextView now where we want to show results.
// This avoids calling findViewById() constantly.
mResult = (TextView)root.findViewById(R.id.example_result);
...
}
#Override
public void onResume() {
// Keep a reference to the AsyncTask so we can properly
// cancel it when our lifecycle events dictate so.
mAsyncRetriever = new RetrieveData(this);
mAsyncRetriever.execute("get_A.php");
}
#Override
public void onPause() {
// If we have a pending data load going on, kill it.
if (mAsyncRetriever != null) {
mAsyncRetriever.cancel(true);
mAsyncRetriever = null;
}
}
#Override
public void onDataLoaded(String[] result) {
// Only pulling the first result provided
mResult.setText(result[0]);
// The RetrieveData is done, get rid of our ref
mAsyncRetriever = null;
}
}
I have the following class extended Fragment:
public static class DummySectionFragment extends Fragment {
public static final String ARG_SECTION_NUMBER = "section_number";
private GridView events;
public DummySectionFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Bundle args = getArguments();
switch (args.getInt(ARG_SECTION_NUMBER)) {
case 1 : return initializeCalendarScreen(inflater);
case 2 : return inflater.inflate(R.layout.todo_lists_layout, null);
}
return null;
}
public void updateGridView() {
int daysInCurrentMonth=(MainActivity.month==1) ? daysInFebruary : COUNT_OF_DAYS_IN_MONTH[MainActivity.month];
int daysInPrevMonth=(MainActivity.month-1==1) ? daysInFebruary : COUNT_OF_DAYS_IN_MONTH[MainActivity.month-1];
int daysInNextMonth=(MainActivity.month+1==1) ? daysInFebruary : COUNT_OF_DAYS_IN_MONTH[MainActivity.month+1];
Calendar calendar=Calendar.getInstance();
calendar.set(MainActivity.year, MainActivity.month, 1);
int startDayOfWeek=calendar.get(Calendar.DAY_OF_WEEK);
List<Integer> dates=new ArrayList<Integer>();
for (int i=0; i<startDayOfWeek-1; i++) {
dates.add(daysInPrevMonth-i);
}
for (int i=0; i<daysInCurrentMonth; i++) {
dates.add(i+1);
}
ArrayAdapter<Integer> adapter=new ArrayAdapter<Integer>(getActivity(), R.layout.grid_view_cell_layout, R.id.textViewGridCell, dates);
events.setAdapter(adapter);
}
private View initializeCalendarScreen(LayoutInflater inflater) {
View layout=inflater.inflate(R.layout.calendar_layout, null);
events=(GridView)layout.findViewById(R.id.gridViewCalendar);
updateGridView();
return layout;
}
}
It works good (I use it for FragmentPagerAdapter). And I use method updateGridView() from Activity for updating value in GridView. But if I do it I will got NullPointerException, because getActivity() for ArrayAdapter returns null. Please, tell me, how can I update Fragment UI from Activity? Thank you
ONE POSSIBLE SOLUTION IS:
Create static global variable context
create context = inflater.getContext().
Use context to create ArrayAdapter when updating UI.
Change:
...=new ArrayAdapter<Integer>(context,...)
Use an Observer in the Fragment instead of calling updateGridView() outside your Fragment. Have an Observer in your Fragment observe an Observable in your main Activity. When the data needs to be updated, just call the notifyObservers() method of the Observable. The Observer class should be a child class of your Fragment and should look something like this:
class UpdateGridObserver implements Observer {
#Override
public void update(Observable observable, Object data) {
// Do stuff like:
// runOnUiThread is REQUIRED because updating the adapter
// and ListView from a background thread is not allowed
getActivity().runOnUiThread(new Runnable(){
updateGridView();
});
}
}
This allows for the data to be updated without having to worry about scope and all that jazz. As long as the Observable object is accessible to the Observer object you should be good to go.
You can extend the Observable class (as with any class) as well to have a custom Observable object if you need more capability than just notification. It's outside the scope of what I think you are looking for, but I've linked some wikis and howtos below.
Observer-Observable Pattern: http://en.wikipedia.org/wiki/Observer_pattern
More: http://www.javaworld.com/jw-10-1996/jw-10-howto.html