I have been searching for an answer to my problem, but I seem to get none, despite of how many tutorials I followed, how many questions I've gone through and how many things I've tried to do what I want. Basically, I stumbled upon some good tips, and still couldn't manage to do what wanted.
THE PROBLEM
I am creating an Android Application that will use Fragments (alongside with tabs). In these fragments, I have crucial information relating the application, such as text boxes, and buttons. However, I want to do something really simple, which is updating one of my fragments as I come back to it (imagine I swipe back to a fragment, and I update it with the relevant information). Where is the information stored? On a node.js server, to which I call every time I want information. So for that, I created the following structure.
THE STRUCTURE
First of all, I started off creating my Activity.
public class CentralActivity extends FragmentActivity {
CentralPagerAdapter mCentralActivity;
ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_central);
tabHandler();
}
public void tabHandler() {
mCentralActivity = new CentralPagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.CentralPager);
mViewPager.setAdapter(mCentralActivity);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
getActionBar().setSelectedNavigationItem(position);
}
});
//Action Bar Stuff
}
}
With this said, I need my CentralPagerAdapter, which I created as follows.
public class CentralPagerAdapter extends FragmentStatePagerAdapter {
private int nSwipes = 3;
public CentralPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new CentralFragment();
Bundle args = new Bundle();
args.putInt(CentralFragment.ARG_OBJECT, i + 1);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return nSwipes;
}
}
And now, my fragment, which is only a class that contains all of my views, and options and so on.
public class CentralFragment extends Fragment {
public static final String ARG_OBJECT = "object";
private View rootView;
private RESTFunction currentFunction;
//Has the info I want
private ArrayList<Integer> tickets = new ArrayList<Integer>();
#SuppressLint("HandlerLeak")
private Handler threadConnectionHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (currentFunction) {
case GET_CLIENT_TICKETS:
handleGetTickets(msg);
break;
case BUY_CLIENT_TICKETS:
break;
default:
break;
}
}
};
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
final Bundle args = getArguments();
handleFragments(inflater, container);
getTicketInfo(null);
return rootView;
}
private void handleFragments(LayoutInflater inflater, ViewGroup container) {
Bundle args = getArguments();
if (args.getInt(ARG_OBJECT) == 1) {
rootView = inflater.inflate(R.layout.fragment_show_tickets,
container, false);
showTicketsHandler();
} else if (args.getInt(ARG_OBJECT) == 2) {
rootView = inflater.inflate(R.layout.fragment_buy_tickets,
container, false);
buyTicketsHandler();
} else {
rootView = inflater.inflate(R.layout.fragment_history_tickets,
container, false);
}
}
public void showTicketsHandler() {
//Get stuff from the tickets array that the REST call will handle
//And set them to boxes or radio buttons
}
public void buyTicketsHandler() {
//Get stuff from the tickets array that the REST call will handle
//And set them to boxes or radio buttons
//As well as button click listeners
}
public void getTicketInfo(ProgressDialog progDialog) {
//Connect to the thread to get the information
//In this case, I have no parameters
ConnectionThread dataThread = new ConnectionThread("myLink", Method.GET, null, threadConnectionHandler, progDialog);
dataThread.start();
}
//Get stuff from the resulting JSON and store it in the tickets ArrayList
private void handleGetTickets(Message msg) {
JSONObject ticketListing = (JSONObject) msg.obj;
try {
tickets.add(ticketListing.getInt("t1"));
tickets.add(ticketListing.getInt("t2"));
tickets.add(ticketListing.getInt("t3"));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
And then, I have my thread..
public class ConnectionThread extends Thread {
private ConnectionRunnable runConnection;
private Handler mHandler;
private ProgressDialog progDialog;
public ConnectionThread(String link, Method method, ArrayList<NameValuePair> payload, Handler handler, ProgressDialog progDialog) {
runConnection = new ConnectionRunnable(link, method.toString(), payload);
mHandler = handler;
this.progDialog = progDialog;
}
#Override
public void run() {
runConnection.run();
threadMsg();
if(progDialog != null)
progDialog.dismiss();
}
public JSONObject getJSON() {
return runConnection.getResultObject();
}
private void threadMsg() {
Message msgObj = mHandler.obtainMessage();
msgObj.obj = getJSON();
mHandler.sendMessage(msgObj);
}
}
And ConnectionRunnable is where I run my HttpURLConnection.
SO WHAT DO I NEED?
Basically, what I'm trying to do, is to get the ticket information from the ConnectionThread BEFORE I load all my view and update them. Plus, I want to be able to swipe back and forth, and update my information on the array as I swipe through the screens (if I go to the second screen, the tickets will update, and if I come back to the first, they will re-update). So basically, call the ConnectionThread everytime I swipe around. If that is possible that, is.
WHAT HAVE I TRIED?
I've tried several things already, and all of them didn't actually help..
The usage of ProgressDialogs to stop the UI Thread on the onCreateView method of the fragment (no use, because it returns the rootView before it handles everything);
Making the UI Thread sleep for 1 second (I don't know why, it blocks all of them);
Overriding the instantiateMethod() of the Adapter, although I think I didn't do it correctly;
Overriding the saveState() of the Adapter, in order to prevent its saved states, and to then get new ticket information;
Giving the fragments tags to update their rootViews on the Adapter, but to no avail;
Getting the information in the activity, and everytime I make a purchase (second fragment), restart the whole activity to get the tickets, which I believe is a really, really bad solution.
I've read several articles, and I still couldn't find my answers.. It's really frustrating. Because it's something so simple, however, the fact that I have to run the HTTP calls on a different thread delays the whole UI updating process.
I've also read the AsyncTask's method. However, I feel like both Threads and AsyncTasks end up in the same.
WHAT TO DO NOW?
Well, that's what I was hoping to find. Because it ends up being annoying as it is.
POSSIBLE REASONS
Is it because I'm separating all classes into spread files, therefore making my work difficult?
Thank you for your time, guys, hope we can find a solution or something.
THE EDIT
So basically, after 4 hours of reading documents and tutorials, I figured that what I needed was setOffscreenPageLimit(int). However, it can't be set to 0, so I will have to do with a setOnPageChangeListener. Now, to figure how to refresh the fragment, and I'll be as good as new.
Alright, it works perfectly! Basically, I did this:
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
((CentralFragment)((CentralPagerAdapter) mViewPager.getAdapter()).instantiateItem(mViewPager, position)).refresh();
getActionBar().setSelectedNavigationItem(position);
}
});
Where my .refresh is:
public void refresh() {
Bundle args = getArguments();
if (args.getInt(ARG_OBJECT) == 0) {
getTicketInfo(0);
} else if (args.getInt(ARG_OBJECT) == 1) {
getTicketInfo(1);
buyTicketsHandler();
} else {
//To Handle Later
}
}
It's as simple as refreshing the page before you go to it. Why didn't I remember this before..? So, here's the reference for those who ever need this!
Related
My RecyclerView crashes with an IndexOutOfBoundsException while scrolling after attempting to refresh the data.
Desired functionality: After API request has successfully populated RecyclerView once, I'd like to refresh the RecyclerView and be able to scroll up and down while it refreshes.
Current functionality: If I don't scroll while refreshing the data, the app doesn't crash. If I scroll after making a refresh request, it crashes with an IndexOutOfBoundsException.
I've spent several weeks trying to troubleshoot this problem without posting a question, and I believe I've tried enough potential solutions to justify asking Stack Overflow for guidance. There are countless questions on here with the same subject, but unfortunately none of them have solved my problem. Thank you in advance for your consideration.
Here are some solutions other people have suggested:
To use adapter.notifyDataSetChanged(), but I understand this to
be considered a 'last resort' in the Android documentation
To call list.clear before adapter.notifyDataSetChanged()
To get the position of all current items in the data set to an Integer called 'position' with adapter.getItemCount(), and then pass that to adapter.notifyItemRangeChanged(position)
To set adapter.setHasStableIds(true)
To call mRecyclerView.getRecycledViewPool().clear() and mAdapter.notifyDataSetChanged();
Apparently if the RecyclerView is inside a LinearLayout, 'notify' methods don't work (this might pertain to an old bug in Android which may be fixed now, but I'm not sure.)
All of these suggestions result in a 'Fatal Exception'.
My app uses five files:
JobsAdapter (Adapter)
JobsListItem (Getters and Setters)
JobsOut (Fragment)
jobs_recyclerview
jobs_listitem
I've only included code for the Adapter and Fragment, because I'm confident that the layout files and Getters and Setters are well formed.
Fragment:
public class JobsOut extends Fragment {
String jobId;
String jobTitle;
String jobNumber;
String jobStartTime;
String dispatchType;
#BindView(R.id.jobsOutRecyclerView) RecyclerView jobsOutRecyclerView;
#BindView(R.id.fab) FloatingActionButton refreshFab;
private List<JobsListItem> dispatch;
private RecyclerView.Adapter mJobsOutAdapter;
public RecyclerView.LayoutManager dispatchLayoutManager;
OkHttpClient client = new OkHttpClient();
Handler handler = new Handler();
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.recycler_test, container, false);
ButterKnife.bind(this, rootView);
dispatch = new ArrayList<>();
jobsOutRecyclerView.setHasFixedSize(true);
dispatchLayoutManager = new LinearLayoutManager(getContext());
jobsOutRecyclerView.setLayoutManager(dispatchLayoutManager);
downloadDispatch();
refreshFab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
downloadDispatch();
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
dispatch.clear();
}
});
}
});
return rootView;
}
#Override
public void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(this);
}
private void downloadDispatch() {
final okhttp3.Request request = new okhttp3.Request.Builder()
.url("url")
.header("X_SUBDOMAIN", "SUBDOMAIN")
.header("X-AUTH-TOKEN", "API_KEY")
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
try {
String jsonData = response.body().string();
JSONObject getRootObject = new JSONObject(jsonData);
JSONObject metaObject = getRootObject.getJSONObject("meta");
final String row_count = metaObject.getString("total_row_count");
{
if (row_count.equals("0")) {
// do something for no jobs
} else {
JSONObject getArray = new JSONObject(jsonData);
JSONArray opportunitiesArray = getArray.getJSONArray("opportunities");
for (int i = 0; i < opportunitiesArray.length(); i++) {
JSONObject opportunity = opportunitiesArray.getJSONObject(i);
jobId = opportunity.getString("id");
jobTitle = opportunity.getString("subject");
jobNumber = opportunity.getString("number");
jobStartTime = opportunity.getString("starts_at");
dispatchType = opportunity.getString("customer_collecting");
// Take Strings from response and send them to JobsListItem
final JobsListItem item = new JobsListItem(jobId, jobTitle, jobNumber, jobStartTime, dispatchType);
// If the adapter hasn't been created, do this
if (mJobsOutAdapter == null) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
mJobsOutAdapter = new JobsAdapter(dispatch, getContext());
jobsOutRecyclerView.setAdapter(mJobsOutAdapter);
dispatch.add(item);
}
});
}
// If the adapter has been created, just do this
else if (mJobsOutAdapter != null) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
dispatch.add(item);
mJobsOutAdapter.notifyDataSetChanged();
}
});
}
}
}
}
} catch (IOException e) {
Log.e("TAG", "IO exception caught: ", e);
} catch (JSONException e) {
Log.e("TAG", "TAG exception caught: ", e);
}
}
});
}
Adapter:
public class JobsAdapter extends RecyclerView.Adapter<JobsAdapter.ViewHolder> {
private List<JobsListItem> mJobsListItem;
private Context context;
public JobsAdapter(List<JobsListItem> mJobsListItem, Context context) {
this.mJobsListItem = mJobsListItem;
this.context = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.jobs_listitem, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
final JobsListItem mJobsListItemViewHolder = this.mJobsListItem.get(position);
// holders go here and do things with text and what-not
}
#Override
public int getItemCount() {
return mJobsListItem.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
// BindView's with ButterKnife go here and all that jazz
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
Logcat from crash:
26404-26404 E/AndroidRuntime: FATAL EXCEPTION: main
Process: uk.co.plasmacat.techmate, PID: 26404
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 4(offset:4).state:16
at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5504)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5440)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5436)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2224)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1551)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1511)
at android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1325)
at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:1061)
at android.support.v7.widget.RecyclerView.scrollByInternal(RecyclerView.java:1695)
at android.support.v7.widget.RecyclerView.onTouchEvent(RecyclerView.java:2883)
at android.view.View.dispatchTouchEvent(View.java:10063)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2630)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2307)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2636)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:413)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1819)
at android.app.Activity.dispatchTouchEvent(Activity.java:3127)
at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:71)
at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:71)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:375)
at android.view.View.dispatchPointerEvent(View.java:10283)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4522)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4353)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3893)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3946)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3912)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4039)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3920)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4096)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3893)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3946)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3912)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3920)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3893)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6341)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6315)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6265)
at
android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6444)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:176)
at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:6415)
at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:6467)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:874)
at android.view.Choreographer.doCallbacks(Choreographer.java:686)
at android.view.Choreographer.doFrame(Choreographer.java:615)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:860)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6290)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
If you have the time, I would thoroughly appreciate your help.
Thank you!
What you’re trying to do is fairly common, your index out of bounds happens when the recycler view needs to ask its adapter for data (because it has scrolled) and the position it needs doesn’t exist in the Adatper. E.g.: the adapter tries to grab item number “N” and the data contains N-1 (or less).
This is most of the times due to a number of factors:
Threading. This should all be handled (for the most part) on the UI Thread (notifications and what not). The network request is obviously happening in a background thread, I think that eventually onResponse is now back on the main thread (otherwise you’d get other exceptions). Double check it my testing Looper.getMainLooper() == Looper.myLooper() (or similar).
You’re doing a lot of (unneeded) work on the Main Thread. You receive the response from the network, and you parse JSON and create objects in the Main Thread… why not offload all the work and once you have a list of items, pass it onto the adapter.
You’re inefficiently calling notifyDataSetChanged() every time (this is bad). Why not use the (included in Android) DiffUtil class to only notify of the changed range? Allow me to point you to a good sample of how it works: https://guides.codepath.com/android/using-the-recyclerview#diffing-larger-changes
It should take you about 30 minutes to implement these changes and it will make your code way more robust.
Bonus points if you use RXJava to make it a stream :-)
note: You should create the adapter once, and then simply call setItems(your_list_of_items) every time you have new data. The DiffUtil and adapter should know how to deal with this. You have a lot of “business logic” there in your activity/fragment/networking code that doesn’t belong there. All your “onResponse” method should do is prepare the data and pass it to the class responsible for managing the data (the adapter). When I see this // If the adapter hasn't been created, do this, I frown. Why is this code creating the adapter here? Who’s gonna test this? What if you change OKHttp with something else? (why not use retrofit and make it even easier?).
I mean, there are multiple things you can do to make your life as a programmer easier, you’re not making use of the solutions available to you.
While there are many good suggestions here. One idea is to just stop the recycler from scrolling when the user interacts with a button that calls additional loading.
recylcerview.stopScroll();
I've searched for a solution for my question all over the internet but I haven't been able to find one and I hope you can help me out
I am trying to create a master detail flow application in android with 2 activities and the second activity contains a fragment. Can anyone please tell me how I can simultaneously update the value in the MainActivity() when I make a change in the fragment's EditText field? I have tried using an Intent but when the 2 activities are side by side that doesnt seem to work well.
Screenshot of Emulator
Any suggestions?
It seems you are in a context as follows:
When A happens, it triggers B
As a result, I suggest you to use EventBus library in your project.
The installation is easy. First, add the following code in your build.gradle file:
compile 'org.greenrobot:eventbus:3.0.0'
Second, let's see what we are going to add in our codes.
In the Fragment which you wanted to make changes:
/* When A happens */
myButton.setOnClickListener(new View.OnClickListener() { // complete entering the content, update it
EventBus.getDefault.post(MyUpdateEvent(myContent));
});
Create your custom class MyUpdateEven:
public class MyUpdateEvent{
private String myContent;
public MyUpdateEvent(String myContent) {
this.myContent = myContent;
}
public String getUpdateContent() {
return myContent;
}
}
In the Activity you wanted to update:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventBus.getDefault.register(this); // add this code to monitor the update
}
/* It triggers B */
#Subscribe // don't forget to add #Subscribe
public void onEvent(MyUpdateEvent event){
// this is your custom method
myTextView.setText(event.getUpdateContent()); // do your update
}
#Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault.unregister(this); // when you leave this lifecycle, cancel the monitoring
}
}
EventBus is a good library that I've been used a lot in my projects.
I think it can solve your problem.
Try to define a interface.
public interface OnEditActivity {
public void onEdit(ActivityObject activityObject, boolean isEditing);
}
And on your another class for example DetailActivity, then you have to override the method onEdit that you created in your interface:
public class DetailActivity extends AppCompatActivity implements OnEditActivity{
//IN HERE --- Create method.
#Override
public void onEdit(ActivityObject activityObject, boolean isEditing) {
if(isEditing){
displayView(activityObject,true);
}else{
displayView(activityObject,false);
}
}
}
And in your EditFragment for example will look like this:
public class EditFragment extends Fragment{
//Define your interface in your fragment
private OnEditActivity onEditActivity;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_edit_activity, container, false);
return v;
}
public void onAttach(Activity a) {
super.onAttach(a);
onEditActivity =(OnEditActivity) a;
}
}
And if you want to call onEdit method just call:
onEditActivity.onEdit(activityObjectNew,false)
I hope this help you !
I've been trying to determine if it's possible to create an observable DialogFragment. Essentially I want to be able to:
Create and show a DialogFragment
Get back an rx Observable which can be subscribed to for the result (ok/cancel pressed, String input, background task success/failure, etc.)
Properly handle configuration change
So far the closest thing I've found is ReactiveDialog, which used to be part of RxAndroid, but has been removed from RxAndroid in v1.0.0 as a part of simplifying RxAndroid.
While ReactiveDialog does appear to meet my first two criteria, it does not appear to handle configuration change. There are two issues to consider:
The DialogFragment must maintain its Observable across configuration change so it can notify subscribers of its state.
The subscriber(s) must be able to either hold on to their subscription or re-subscribe after a configuration change (obviously without producing a memory leak).
I'm still fairly new to RxJava, so I'm still trying to wrap my head around how you would manage something like this. It seems like it should be possible, but I feel like it would require a static or singleton Observable manager and possibly retainedInstance DialogFragments.
Anyone have any suggestions or best practices for this?
There are two issues here; one is that you don't want to lose Java Objects during relayout - look into the runtime changes docs about that.
The other issue is that you want to create an Observable that has the action of the dialog, when that action is triggered. For that, have a look at the RxJava docs, the Asynchronous Observer example. You will need to create an Observable.OnSubscribe, and pass that Subscriber to your code that will call the necessary onNext/onError/onCompleted calls.
I would use a ViewModel for the dialog which helps with configuration changes. After a configuration change re-subscribe to the dialog's ViewModel.
1. Components
Screen (Activity/Fragment) - This will display the dialog fragment
DialogFragment - The dialog. Will publish updates about User's actions.
DialogViewModel - holds the User's actions stream
2. Implementation
SimpleActivity
public class SimpleActivity extends AppCompatActivity {
private SimpleDialogViewModel dialogViewModel;
private CompositeDisposable compositeDisposable;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dialogViewModel = ViewModelProviders.of(this).get(SimpleDialogViewModel.class);
compositeDisposable = new CompositeDisposable();
showDialog();
}
#Override
protected void onResume() {
super.onResume();
Disposable disposable =
dialogViewModel
.actionStream()
.subscribe(
result -> {
if (AlertDialog.BUTTON_POSITIVE == result) {
// User clicked yes
}
if (AlertDialog.BUTTON_NEGATIVE == result) {
// User clicked no
}
}
);
compositeDisposable.add(disposable);
}
#Override
protected void onPause() {
super.onPause();
compositeDisposable.clear();
}
private void showDialog() {
SimpleDialogFragment dialogFragment = new SimpleDialogFragment();
dialogFragment.show(getSupportFragmentManager(), SimpleDialogFragment.TAG);
}
}
SimpleDialogFragment
public class SimpleDialogFragment extends DialogFragment {
public static final String TAG = "SimpleDialogFragment";
private SimpleDialogViewModel dialogViewModel;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dialogViewModel = ViewModelProviders.of(getActivity()).get(SimpleDialogViewModel.class);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.dialog_simple_message, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
final View btnYes = view.findViewById(R.id.yes);
final View btnNo = view.findViewById(R.id.no);
btnYes.setOnClickListener(v -> dialogViewModel.onClickYes());
btnNo.setOnClickListener(v -> dialogViewModel.onClickNo());
}
}
SimpleDialogViewModel
public class SimpleDialogViewModel extends ViewModel {
private Subject<Integer> actionSubject;
SimpleDialogViewModel() {
actionSubject = PublishSubject.create();
}
public void onClickYes() {
actionSubject.onNext(AlertDialog.BUTTON_POSITIVE);
}
public void onClickNo() {
actionSubject.onNext(AlertDialog.BUTTON_NEGATIVE);
}
public Observable<Integer> actionStream() {
return actionSubject;
}
}
I have a Page Viewer with three pages with ArrayLists in them, represented as "Tomorrow" "Today" and "Yesterday". I also have a Drawer that allows to change settings of the Lists.
When the Drawer closes, I want the ListsViews (or the entire page fragment) to update to show three new ArrayLists that were created after the new settings were applied.
So far, I managed to be able to update "Yesterday" and "Tomorrow" (When sliding to yesterday, tomorrow updates and vice versa), I think that is because "Today" never gets destroyed.
Either way, I would really like to see all three update as soon as the Drawer closes.
Here is the code for my Adapter:`
private class MyPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
ListFragmentOfTomorrow torrowFragment =new ListFragmentOfTomorrow();
ListFragmentOfToday todayFragment = new ListFragmentOfToday();
ListFragmentOfYesterday yestFragment = new ListFragmentOfYesterday();
public MyPagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int pos)
{
switch (pos) {
case 0:
torrowFragment.newInstance(tomorrowArrayList, MainActivity.this);
registeredFragments.put(0, torrowFragment);
return torrowFragment;
case 1:
todayFragment.newInstance(todayArrayList, MainActivity.this);
registeredFragments.put(1, todayFragment);
return todayFragment;
case 2:
yestFragment.newInstance(yesterdayArrayList, MainActivity.this);
registeredFragments.put(2, yestFragment);
return yestFragment;
default:
todayFragment.newInstance(todayArrayList, MainActivity.this);
registeredFragments.put(3, todayFragment);
return todayFragment;
}
}
#Override
public int getCount() {
return 3;
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}`
This is the code for one of the page Fragment (all three are basically the same):
public class ListFragmentOfToday extends Fragment
{
static ExpandListAdapter ExpAdapter;
static ExpandableListView expndList;
static Context context;
static ArrayList<Game> todayArrayList;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_list_of_today, container, false);
expndList = new ExpandableListView(context);
expndList = (ExpandableListView)v.findViewById(R.id.FragmentedExpandableListView);
ExpAdapter = new ExpandListAdapter(context,todayArrayList);
expndList.setAdapter(ExpAdapter);
return v;
}
public static ListFragmentOfToday newInstance(ArrayList<Game> todayArrayListIn, Context contextIn)
{
ListFragmentOfToday todayFragment = new ListFragmentOfToday();
context = contextIn;
todayArrayList = todayArrayListIn;
return todayFragment;
}
public void RefreshList(ArrayList<Game> todayArrayListIn)
{
todayArrayList=todayArrayListIn;
ExpAdapter.notifyDataSetChanged();
}
}
This is the code for when the Drawer closes:
#Override
public void onDrawerClosed(View drawerView)
{
//Here, the new ArrayLists are created(...)
ListFragmentOfTomorrow tomorrowFragmentToUpdate = (ListFragmentOfTomorrow)pagerAdapter.getRegisteredFragment(0);
tomorrowFragmentToUpdate.RefreshList(updatedTomorrowArrayList);
ListFragmentOfToday todayFragmentToUpdate = (ListFragmentOfToday)pagerAdapter.getRegisteredFragment(1);
todayFragmentToUpdate.RefreshList(updatedTodayArrayList);
ListFragmentOfYesterday yesterdayFragmentToUpdate = (ListFragmentOfYesterday)pagerAdapter.getRegisteredFragment(2);
yesterdayFragmentToUpdate.RefreshList(updatedYesterdayArrayList);
}
Question: How can I get all three pages to show the new updated arraylists as soon as the drawer closes?
As a new developer and a new StackOverflow user, I would also like to get any feedback on my code writing and my question format. Thank you.
Thank you Thomas! That worked. I tried notifyDataSetChanged() and getItemPosition(Object object), but never together.
dhke - I saw the instantiating fragments when I was looking for an answer, but understood the difference to newInstance()...
Before i start, i have look through question such as:
Passing data between fragments: screen overlap
How to pass values between Fragments
as well as Android docs:
http://developer.android.com/training/basics/fragments/communicating.html
as well as this article:
http://manishkpr.webheavens.com/android-passing-data-between-fragments/
Though all the cases mentioned above similar to what i have, it is not entirely identical. I followed a good tutorial here (Some portion of my code is based on this article):
http://www.androidhive.info/2013/10/android-tab-layout-with-swipeable-views-1/
I have the following files:
RegisterActivity.java
NonSwipeableViewPager.java
ScreenSliderAdapter.java
RegisterOneFragment.java
RegisterTwoFragment.java
And the following layouts:
activity_register.xml
fragment_register_one.xml
fragment_register_two.xml
What i am trying to achieve is passing an Serializable object from RegisterFragmentOne to RegisterFragmentTwo.
So far this is what i have done (some codes are omitted):
RegisterActivity.java
public class RegisterActivity extends FragmentActivity
implements RegisterOneFragment.OnEmailRegisteredListener{
public static NonSwipeableViewPager viewPager;
private ScreenSliderAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
// Initilization
mAdapter = new ScreenSliderAdapter(getSupportFragmentManager());
viewPager = (NonSwipeableViewPager) findViewById(R.id.pager);
viewPager.setAdapter(mAdapter);
}
public void onEmailRegistered(int position, Registration regData){
Bundle args = new Bundle();
args.putSerializable("regData", regData);
viewPager.setCurrentItem(position, true);
}
}
ScreenSliderAdapter.java
public class ScreenSliderAdapter extends FragmentPagerAdapter{
public ScreenSliderAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new RegisterOneFragment();
case 1:
return new RegisterTwoFragment();
case 2:
return new RegisterThreeFragment();
}
return null;
}
#Override
public int getCount() {
return 3;
}
}
NonSwipeableViewPager.java (extending ViewPager class, and overrides the following)
#Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
// Never allow swiping to switch between pages
return false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// Never allow swiping to switch between pages
return false;
}
RegisterOneFragment.java
public class RegisterOneFragment extends Fragment {
OnEmailRegisteredListener mCallBack;
public interface OnEmailRegisteredListener {
/** Called by RegisterOneFragment when an email is registered */
public void onEmailRegistered(int position, Registration regData);
}
public void onAttach(Activity activity){
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception.
try {
mCallBack = (OnEmailRegisteredListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEmailRegisteredListener");
}
}
... And some to execute some HTTP request via separate thread...
}
What i am trying to accomplish is that when ever a user pressed a button on RegisterOneFragment, a data will be sent to a server (and returns some validation over JSON). If the returned data is valid, the the application should go to the next fragment which is RegistrationTwoFragment.
I am having some confusion as how to pass objects between fragments, since my Fragments is created using an Adapter. And that Adapter is then attached to my Activity.
Can anyone help me with this? Thx
Edit 1:
I tried to make a shortcut (unfortunately does not work) like so:
In RegisterActivity i created:
public Registration regData;
and in RegisterOneFragment:
/* PLACED ON POST EXECUTE */
((RegisterActivity)getActivity()).regData = regData;
Finally called it in RegisterTwoFragment
Registration regData;
regData = ((RegisterActivity) getActivity()).regData;
It throws a nullPointerExceptions
Edit 2
Just to be clear, RegisterActivty contains multiple fragments. And the only way user can navigate between fragment is by clicking a button. The Activity has no Tab bar.
It's easy to share objects via implementing Serializable to your custom Object. I wrote a tutorial about this here.
From Fragment One:
android.support.v4.app.FragmentTransaction ft =
getActivity().getSupportFragmentManager().beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
OfficeCategoryFragment frag = new OfficeCategoryFragment();
Bundle bundles = new Bundle();
Division aDivision = divisionList.get(position);
// ensure your object has not null
if (aDivision != null) {
bundles.putSerializable("aDivision", aDivision);
Log.e("aDivision", "is valid");
} else {
Log.e("aDivision", "is null");
}
frag.setArguments(bundles);
ft.replace(android.R.id.content, frag);
ft.addToBackStack(null);
ft.commit();
In Fragment two:
Bundle bundle = getArguments();
Division division= (Division) bundle.getSerializable("aDivision");
Log.e("division TEST", "" + division.getName());
I would normally have setters or methods similar to this in the containing activity.
So if I understand correctly, you want the user to access RegistrationOneFragment, then when completed, use this data, validate it, and if valid, pass it along to RegistrationTwoFragment and move the user to this Fragment.
Could you simply call validateJson(regData) in your onEmailRegistered method to handle the validation in your activity, if it succeeds, commit a transaction to RegistrationTwoFragment.
Then all you need are getters and setters in your activity or Fragment to say getRegistrationOneData() in the activity or setData(Registration args) in the fragment as your examples show above.
I don't know of any way to pass the args directly into the Fragment.
I found a solution to my question, which i am sure not the correct way to do that...
So in RegisterActivity.java i add + modified the following lines (thx to #sturrockad):
public Registration getRegistrationData(){
return this.regData;
}
public void onEmailRegistered(int position, Registration regData){
this.regData = regData;
viewPager.setCurrentItem(position, true);
}
Then in RegisterTwoFragments.java (or in the Fragment to which i want to receive the Object):
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_register_two, container, false);
regData = ((RegisterActivity) getActivity()).getRegistrationData();
...
I used to set object with Pacelable or Serializable to transfer, but whenever I add other variables to object(model), I have to register it all. It's so inconvenient.
It's super easy to transfer object between activities or fragments.
Android DataCache
put your data object to KimchiDataCache instance in your activity or fragment.
User userItem = new User(1, "KimKevin"); // Sample Model
KimchiDataCache.getInstance().put(userItem);
// add your activity or fragment
Get your data object in your activity of fragment that you added.
public class MainFragment extends Fragment{
private User userItem;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userItem = KimchiDataCache.getInstance().get(User.class);
}