I am using BottomNavigationBar and switch fragments with this. However, animations start to work only after the second showing of the same fragment. If you do not understand what, watch this video: https://drive.google.com/file/d/1AEAO_-wG2k17sMvpbrHu3UjlA4iAi6DQ/view?usp=sharing
onCreate():
fm = getSupportFragmentManager().beginTransaction();
fm.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fm.add(R.id.gen_fr, settings).hide(settings);
fm.add(R.id.gen_fr, events).hide(events);
fm.add(R.id.gen_fr, refs).hide(refs);
fm.add(R.id.gen_fr, earn);
fm.commit();
When switching fragments:
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
fm = getSupportFragmentManager().beginTransaction();
fm.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
switch (item.getItemId()) {
case R.id.main_room:
if (active != earn) {
fm.hide(active).show(earn);
active = earn;
}
break;
case R.id.refs_room:
if (active != refs) {
fm.hide(active).show(refs);
active = refs;
}
break;
case R.id.events_room:
if (active != events) {
fm.hide(active).show(events);
active = events;
}
break;
case R.id.settings_room:
if (active != settings) {
fm.hide(active).show(settings);
active = settings;
}
break;
}
fm.commit();
return true;
My MainActivity has a Drawer and instantiates a new Fragment, depending on the clicked MenuItem (based on this tutorial).
I monitored the memory, which is slightly increasing on every Fragment change and I worry, that fragmentClass.newInstance() is not the right way.
// MainActivity
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
Fragment fragment = null;
Class fragmentClass = null;
if (id == R.id.nav_camera) {
fragmentClass = CameraFragment.class;
} else if (id == R.id.nav_gallery) {
fragmentClass = GalleryFragment.class;
} else if (id == R.id.nav_slideshow) {
fragmentClass = SlideshowFragment.class;
} else if (id == R.id.nav_manage) {
fragmentClass = ManageFragment.class;
} else if (id == R.id.nav_share) {
fragmentClass = ShareFragment.class;
} else if (id == R.id.nav_send) {
fragmentClass = SendFragment.class;
}
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.layout_main, fragment).commit();
}
I would expect something like:
// Pseudocode
if (fragmentManager.instanceExists(fragmentClass)) {
// load instantiated fragment
} else {
// newInstance()
}
How could I avoid memory leaks or is this even related?
Thank you!
Practically, there is no problems in that code. Things you can optimize are:
Reworking Fragment#instantiate() method (check docs)
Caching instances in something like Map in order not to create the new one every time you navigate through the drawer
Also, you can check if fragment exists in FragmentManager using FragmentManager#findFragmentById() or FragmentManager#findFragmentByTag()
I make the application "Phone book".
The application has a menu, which is sorting by name or category. I do it like this in MainActivity:
Bundle bundle = new Bundle();
Fragment lvFragSortBy = new ListViewFragment();
switch (item.getItemId()) {
case R.id.menuSortOrderName:
item.setChecked(true);
bundle.putString("sortBy", "name");
lvFragSortBy.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.replace(R.id.fragContainer, lvFragSortBy)
.commit();
return true;
case R.id.menuSortOrderCategory:
item.setChecked(true);
bundle.putString("sortBy", "category");
lvFragSortBy.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.replace(R.id.fragContainer, lvFragSortBy)
.commit();
return true;
In ListViewFragment:
Bundle sortBundle = getArguments();
if (sortBundle != null) {
orderBy = sortBundle.getString("sortBy");
}
And then:
protected ArrayList<Contact> doInBackground(Void... params) {
try {
db = new DbWorker(ctx);
contacts = (ArrayList<Contact>) db.selectAllContacts(orderBy);
} catch (Exception e) {
Log.e(TAG, "Exception in AsyncTask.doInBackground");
}
return contacts;
}
This code works fine but I would like to know can it be done better?
Maybe there's another way.
Thanks to everybody, but I found the answer here: https://codereview.stackexchange.com/questions/147878/the-sorting-in-the-app-with-fragments/147881#147881
For proper operation of the fragments, you must use newInstance() pattern.
I`ve got an activity with ViewPager. There is 1 Fragment class - ScheduleFragment and a BroadcastReceiver in it. What I need is to get info and fill up the list in the fragment, based on its position.
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
ScheduleFragment scheduleFragment = new ScheduleFragment();
return scheduleFragment.newInstance(position + 1);
}
#Override
public int getCount() {
// Show 5 total pages.
return 5;
}
#Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.descr_monday);
case 1:
return getString(R.string.descr_tuesday);
case 2:
return getString(R.string.descr_wednesday);
case 3:
return getString(R.string.descr_thursday);
case 4:
return getString(R.string.descr_friday);
}
return null;
}
And here the ScheduleFragment`s methods:
public ScheduleFragment newInstance(Integer day) {
ScheduleFragment fragment = new ScheduleFragment();
Bundle args = new Bundle();
args.putInt("day", day);
fragment.setArguments(args);
return fragment;
}
private class ScheduleBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ArrayList<Lesson> result = (ArrayList<Lesson>) intent.getSerializableExtra("schedule");
ArrayList<Lesson> dataToInsert = new ArrayList<>();
Log.i("ASSSSSSSSSSSS", day.toString());
for (Lesson lesson : result) {
if (Objects.equals(lesson.getDay(), day)) {
dataToInsert.add(lesson);
}
}
adapter = new ScheduleListAdapter(context, result);
SwingBottomInAnimationAdapter swingBottomInAnimationAdapter = new SwingBottomInAnimationAdapter(adapter);
swingBottomInAnimationAdapter.setAbsListView(schedule_listView);
schedule_listView.setAdapter(swingBottomInAnimationAdapter);
adapter.notifyDataSetChanged();
}
}
I also have a Spinner in Toolbar with weeks. So also I want to update the fragment when I pick another week.
Please, guys. Help me. I`ve tried some solutions, but none of them helped me!
Hope for you assistance
I'm probably not an expert on this, but I just completed a project full of fragments. The 'dirty' way I passed data between them was accomplished by using a singleton class in my project. This probably isn't the best solution, though.
For instance, my project included a fragment which I updated based on options in a settings activity. The FamilyMap (familyMap) class is my singleton, and I kept a settings model containing maps of needing information within that object. The code shown here is updating that settings model based on user interaction.
public void mapTypeSelection(String selection)
{
Log.d("Map Selection", selection);
switch(selection)
{
case "Normal":
familyMap.getMapFrag().getmMap().setMapType(GoogleMap.MAP_TYPE_NORMAL);
familyMap.getSettings().setMapType(new Pair<String, Integer>("Normal", GoogleMap.MAP_TYPE_NORMAL));
break;
case "Satellite":
familyMap.getMapFrag().getmMap().setMapType(GoogleMap.MAP_TYPE_SATELLITE);
familyMap.getSettings().setMapType(new Pair<String, Integer>("Satellite", GoogleMap.MAP_TYPE_SATELLITE));
break;
case "Hybrid":
familyMap.getMapFrag().getmMap().setMapType(GoogleMap.MAP_TYPE_HYBRID);
familyMap.getSettings().setMapType(new Pair<String, Integer>("Hybrid", GoogleMap.MAP_TYPE_HYBRID));
break;
case "Terrain":
familyMap.getMapFrag().getmMap().setMapType(GoogleMap.MAP_TYPE_TERRAIN);
familyMap.getSettings().setMapType(new Pair<String, Integer>("Terrain", GoogleMap.MAP_TYPE_TERRAIN));
break;
default:
break;
}
}
//private method of your class
private int getIndex(Spinner spinner, String myString)
{
int index = 0;
for (int i=0;i<spinner.getCount();i++){
if (spinner.getItemAtPosition(i).toString().equalsIgnoreCase(myString)){
index = i;
break;
}
}
return index;
}
public Pair<String, Integer> colorLineSelection(String selection)
{
Log.d("Map Selection", selection);
switch(selection)
{
case "Red":
return new Pair<String, Integer>("Red", Color.RED);
case "Blue":
return new Pair<String, Integer>("Blue", Color.BLUE);
case "Green":
return new Pair<String, Integer>("Green", Color.GREEN);
default:
return null;
}
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
switch(parent.getId()){
case R.id.mapTypeSpinner:
mapTypeSelection((String)parent.getItemAtPosition(position));
break;
case R.id.lifeLineSpinner:
FamilyMap.getInstance().getSettings().setLifeColor(colorLineSelection((String)parent.getItemAtPosition(position)));
familyMap.getMapFrag().drawLines();
break;
case R.id.spouseLineSpinner:
FamilyMap.getInstance().getSettings().setSpouseColor(colorLineSelection((String) parent.getItemAtPosition(position)));
familyMap.getMapFrag().drawLines();
break;
case R.id.familyLineSpinner:
FamilyMap.getInstance().getSettings().setFamilyColor(colorLineSelection((String) parent.getItemAtPosition(position)));
familyMap.getMapFrag().drawLines();
break;
default:
break;
}
}
The other thing I did was I retained the instance of my fragment - it was the main portion of my app, so it made sense for my situation. That allowed me to be able to update the original fragment when an option was selected. I also set the fragment to update when a certain result was reached by using startActivityForResult().
I think a better (cleaner, more acceptable) option for sending the data to your fragment would be object serialization, but at least in my case, that proved to be more work than I could afford at the time.
Hopefully this at least helps to point you in the right direction!
So, after trying, trying and a lot of trying I figured it out!
In newInstance method, where I create new instance of Fragment I`ve added a bundle with day, according to fragments position
public ScheduleFragment newInstance(Integer day) {
ScheduleFragment fragment = new ScheduleFragment();
Bundle args = new Bundle();
args.putInt("day", day);
fragment.setArguments(args);
return fragment;
}
Then, in fragment's onCreateView I get this argument with getArguments().getInt("day"); and put it into private Integer dayglob; field, which means, that its different for every fragment.
Now, when I have 'day' variable in my fragment I can put info into list according to provided day:
private class ScheduleBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ArrayList<Lesson> result = (ArrayList<Lesson>) intent.getSerializableExtra("schedule");
ArrayList<Lesson> dataToInsert = new ArrayList<>();
for (Lesson lesson : result) {
if (Objects.equals(lesson.getDay(), dayglob)) {
dataToInsert.add(lesson);
}
}
adapter = new ScheduleListAdapter(context, dataToInsert);
SwingBottomInAnimationAdapter swingBottomInAnimationAdapter = new SwingBottomInAnimationAdapter(adapter);
swingBottomInAnimationAdapter.setAbsListView(schedule_listView);
schedule_listView.setAdapter(swingBottomInAnimationAdapter);
try {
getActivity().unregisterReceiver(mScheduleBroadcastReceiver);
} catch (IllegalArgumentException e) {
Log.i("Unregister", e.toString());
}
}
}
Every fragment got its own "onReceive" and wor on data from service for specific fragment.
Also, about the week spinner and updating the lists according to its value: I've made a static field public static Spinner week_spinner;, initialized it it onCreate method of my Activity and then, in fragment's "onCreateView" I made a request to my database like this:
requestQueue.add(connectionManager.getSchedule(this, savedUser.getGroup(), MainActivity.week_spinner.getSelectedItemPosition() + 1));
The only matter thing here is getting value by accessing spinner via static reference.
And, also, I've set Listener which says to fragments something like
Hey, guys! You are not up to date! Update yourself!
week_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mViewPager.getAdapter().notifyDataSetChanged();
}
});
And a little trick which make it possible is to change getItemPositionin FragmentPagerAdapter class (SectionsPagerAdapter in my case)
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
It`s kinda tricky, I guess, but its the only way i can figure it out
I hope, this will help someone!
i have an activity with a navigation drawer built using the library https://github.com/mikepenz/MaterialDrawer
I want to load 4 different pages from selecting 4 different items from the navigation drawer.
I decided to use fragments for each item in the navigation drawer, so when any item is pressed, a new fragment for that option is created and shown to the user.
What i want is that, regardless of which fragment the user is currently in, or which fragment he came from, whenever he presses the back button, he is returned to the original activity.
This is the code i have so far, but apparently the Fragment Backstack is not being updated.
result = new DrawerBuilder().withSelectedItem(-1)
.withActivity(this)
.withToolbar(toolbar)
.withAccountHeader(headerResult)
.addDrawerItems(
item1,item2,item3,item4
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
#Override
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
result.setSelection(position,false);
clearStack();
switch (position) {
case 1:
fragment = new admin_fragment_data_entry();
break;
case 2:
fragment = new admin_fragment_data_edit();
break;
case 3:
break;
case 4:
break;
}
initiateFragment();
return false;
}
}
)
.build();
#Override
public void onBackPressed() {
int count = getFragmentManager().getBackStackEntryCount();
//handle the back press :D close the drawer first and if the drawer is closed close the activity
if (result != null && result.isDrawerOpen()) {
result.closeDrawer();
}
else if (count == 0) {
super.onBackPressed();
//additional code
}
else {
getFragmentManager().popBackStack();
}
}
private void initiateFragment(){
if (fragment != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.addToBackStack(null);
ft.replace(R.id.container_body, fragment);
ft.commit();
}
}
private void clearStack(){
int count = getFragmentManager().getBackStackEntryCount();
while(count > 0){
getFragmentManager().popBackStack();
count--;
}
}
Any help would be highly appreciated.
Also, should i have used fragments here? Or i should have just started 4 different activities for 4 different options from navigation drawer?
Are you using the correct fragment manager to get your backstack count?
I notice in other parts of your class you're using getSupportFragmentManager instead.