In my app fragment(first fragment) where user can press on item and app will show fragment with list(second fragment), where user can drop caught item.
Code:
public void startDragNDrop(){
showFragmentWithList();
JSONObject object = new JSONObject();
object.put(Constants.PARAM_ID, getId());
ClipData data = ClipData.newPlainText("", object.toString());
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(mMainLayout);
mMainLayout.startDrag(data, shadowBuilder, mMainLayout, 0);
}
And when second fragment catches ACTION_DROP it will close himself. And it works well, but if user remove finger faster than method finish their work, dragndrop won't start and second fragment won't receive dragndrop event, as result new fragment won't disappear.
I tried to fix it via setting dragndrop listener at first fragment and when it will catch ACTION_DRAG_STARTED it will call showFragmentWithList() and fragment will show. But I faced of new problem, View.OnDragListener doesn't work for any views inside of second fragment. Can somebody help me with this problem?
You should start a drag-n-drop action prior to showing the second fragment, not the other way around. This is a natural order of events. This way you won't end up in a situation when the second fragment has already been shown but the actual drag-n-drop hasn't started yet.
It's hard to tell from the question where is the root of the problem but I would suggest trying to use a UI handler instead of calling methods directly. This way events will go to the UI message queue and thus, will be dispatched after the system events such as onStart(), onResume() and so forth. This might fix the problem of not receiving events in the second fragment.
Also please make sure your first fragment doesn't "steal" those drag events from the second fragment. Maybe it's your first fragment who receives ACTION_DROP and that's why the second one doesn't. Hope that helps.
Related
This is an odd error which I'm battling with at the moment.
I have an activity that displays a search fragment once it has completed initialisation. The search fragment contains a listview to display the results and that listview is inside a SwipeRefreshLayout so that the user can refresh the search results.
If the user selects an item from the list the search fragment is removed and the parent activity is displayed (it has other fragments). The user can choose to open the search fragment and refresh the results if they wish.
The behaviour I have is that if they use the swipe refresh when the fragment is first opened it works as expected. However, if they dismiss the search fragment and then open it again and then swipe down for refresh I get the java.lang.IllegalStateException: Fragment already added exception thrown for the search fragment.
The code to introduce the search fragment the first time is:
#Override
protected void onPostExecute(Void empty) {
dismissSearchProgress();
getFragmentManager().beginTransaction().
setCustomAnimations(R.animator.slide_in_left, 0, 0, R.animator.slide_out_left).
add(R.id.main_vwContent, mWoSearchFragment).commit();
}
The second time the code is introduced via a swipe action on the screen:
case MotionEvent.ACTION_UP:
if (swipeInRange) {
/* do some other stuff */
getFragmentManager().beginTransaction().setCustomAnimations(R.animator.slide_in_left, 0, 0, R.animator.slide_out_left).add(R.id.main_vwContent, mWoSearchFragment).commit();
}
break;
There are no errors thrown in the second instance unless they swipe down to refresh. The error is thrown before the onRefresh event fires.
Anyone have any ideas? Not sure what code is attempting to add the search fragment again as the exception does not have any of my code in the stack trace and the debugger isn't catching anything.
It's amazing what writing up a problem will do for you. The issue was that to open the fragment the user has to swipe from a particular screen location. So on MotionEvent.ACTION_DOWN we check to see if they have started within the target area and set a flag. Then on 'MotionEvent.ACTION_UP' the fragment is added. However, in the case of a SwipeRefreshLayout it intercepts the 'MotionEvent.ACTION_DOWN' event however it pushes the 'MotionEvent.ACTION_UP' up the stack.
So in my case, the flag was still set to true as the last action taken before the refresh was to swipe to add the fragment.
The fix was to ensure that the flag was reset to false when the search fragment was added.
I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).
I currently have the following situation:
1) The "main view" which contains the EditText I'm trying to update. (Let's call it mainView)
2) A fragment that is opened whenever I click in a button that is contained in the "main view", the
fragment receives mainView as parameter.
3) An OnClickListener which is set to a button that is contained by the fragment. This listener receives the fragment as parameter.
Basically what I need to do is, each time I click on the button that triggers the listener, I need to update the editText, however it doesn't seem to be working. I believe it has something to do with "notifying" the view, but I haven't been able to get it working no matter what I try. After I update the text I close the fragment and
Basically the code is the following:
public void onClick(View v){
String newMessageContent = "hello world";
fragment1.mainView.editText1.setText(newMessageContent);
FragmentManager manager = this.fragment1.getActivity().getFragmentManager();
manager.beginTransaction().replace(R.id.container,this.fragment1.mainView.getPlaceHolderFragment()).commit();
}
Please note that I have simplified the problem a little bit and changed the name of the fragment/views in order for you guys to understand better. The text "hello world" is actually dynamic, and depends no another parameter that is received by the OnClickListener.
After I click the fragment does get replaced, so I know the onClickListener is working correctly, however I believe there's something wrong with the way the data change is being notified.
I've already looked at many SO questions, however none of them have helped me to achieve what I need.
Any help is appreciated, thanks.
I suggest implementing an interface, say, IUpdateFromFragment with method, say, onUpdate(String message), then let activity implement that interface and inside the fragment just call something like ((IUpdateFromFragment)this.getActivity()).onUpdate(newMessageContent);
I realized the problem was that each time I replaced the fargment via the fragmentManager, the method setActivityView was being called again, which replaced the EditText content.
In order to avoid this, I manually removed the fragment (instead of replacing it), doing the following:
FragmentManager manager = this.selectTemplateFragment.getActivity().getFragmentManager();
manager.beginTransaction().remove(this.selectTemplateFragment).commit();
Update the fragment via transaction, then within the fragment1 class OnViewCreated, you can do mainView.editText1.setText("whatever");
The way you're doing this now, I'm surprised isn't throwing an exception since the view isn't inflated yet.
I have an app setup that uses fragments in various place, in one case a fragment exists and upon pressing a button another replaces it using replace() all normal so far, however when the user presses the android back button and the new fragment is removed/popped (whatever the system uses) and the app returns to the first fragment there is no event being fired that i can override to perform an action.
is this normal?, the docs seem to suggest that onResume should be fired in this instance, other sites don't, and my app doesn't, if anyone has any clue if this is normal behaviour or not that be great and if it is what event can i hook into?
Edit: i forgot that all of this is occuring as nested fragments, the first fragment is the child of another so is added using a child fragment manager the second replaces the first using its regular fragment manager, besides the issue im describing this is working great
how the first fragment is originally put in place
Menu_Fragment menufragment = new Menu_Fragment();
getChildFragmentManager().beginTransaction().replace(R.id.menu_container, menufragment, "_menu_fragment").commit();
How the second fragment is "added"
Google_Map_Container_Fragment mapcontainerfrag = new Google_Map_Container_Fragment();
getFragmentManager().beginTransaction().replace(R.id.menu_container, mapcontainerfrag, "addedmap").addToBackStack(null).commit();
In my application, I have tabbar functionality. In one tab i am displaying server data in lisview, and on clicking on that detail page for that list item will be open in new fragment.
But when I press back button from that detail page, every time oncreateview called of previous page, so every time listview created and new fetches new server data. So how to prevent such and just display previous state when back button press?
I know it has been too long to give this answer but what i am guessing is you are replacing your fragment with other one. I mean to say is you are using
ft.replace(R.id.realTabContent, fragment);
to move to other fragment which is you are using in your onItemClick so simple solution is use
ft.add(R.id.realTabContent, fragment);
instead of replacing your fragment.
Understand the difference between replace and add. This will solve your problem.
Replace : it will replace the original fragment and re-create the view when you come back
Add : it will just add a new fragment to stack.
Hope this will help someone who is facing the same problem...
I don't think prevent calling onCreateView is a good idea, beside if the function is not called, there will be exception, so NO, you shouldn't. Instead of doing that, I suggest you move your "listview created and new fetches new server data" into another place where you can call explicitly (when you want, where you want, onCreate() is a good place), don't put it in onCreateView(). Hope this helps.
You should cache your data objects apart from view variables as their references needs to be updated if you are fetching any data from server and don't want to make a call again then use branching to branch out that code.
Create an init() method where you do all initialization and server calls and logically branch out call to init() method whenever you don't want to call init().