Lag when switching tabs in BottomNavigationView - java

I have an Activity that contains a BottomNavigationView, and this bottomnav helps the activity to display three fragments. These fragments load well, and I use an AsyncTask to do every heavy operation, while in the UI thread, I show a ProgressBar until everything loads.
There is a weird behaviour with my fragment: The first time I load the fragment it takes some time to actually display it, instead of displaying it instantly with a progressbar.
This thing only happens the first time, and only in this fragment.
The fragment code only contains this:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
new LoadData(getView(), getContext()).execute();
}
private class LoadData extends AsyncTask<Void, Void, Void> {
private View v;
private Context context;
public LoadData(View v, Context context) {
items = new ArrayList<>();
this.v = v;
this.context = context;
}
#Override
protected Void doInBackground(Void... voids) {
setItems(context); //Heavy operation
adapter = new DashAdapter(items, context);
return null;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
//shows progressbar
progress = v.findViewById(R.id.DFProgress);
progress.setVisibility(View.VISIBLE);
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
setPager();
//sets viewPager and hides progressbar
progress.setVisibility(View.GONE);
}
}
In the gif below, if you look at the bottomnavigationview at the bottom, you can see that it takes time to display the fragment. But after trying to load the fragment a second time, it loads as expected.
How could I make the fragment to load the right way?

I had the same problem. I have two options.
Use postdelay when you call LoadData or
First add all fragments with manually. You manage navigationItemSelected yourself.
Like this:
val firstFragment: Fragment = FirstFragment()
val secondFragment: Fragment = SecondFragment()
val thirdFragment: Fragment = ThirdFragment()
val navView: BottomNavigationView = findViewById(R.id.nav_view)
var active = firstFragment
fm.beginTransaction().add(R.id.nav_host_fragment, thirdFragment, "3").hide(thirdFragment).commit()
fm.beginTransaction().add(R.id.nav_host_fragment, secondFragment, "2").hide(secondFragment).commit()
fm.beginTransaction().add(R.id.nav_host_fragment, firstFragment, "1").commit()
navView.setOnNavigationItemReselectedListener { }
navView.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_first -> {
fm.beginTransaction().hide(active).show(firstFragment).commit()
active = firstFragment
}
R.id.navigation_second -> {
fm.beginTransaction().hide(active).show(secondFragment).commit()
active = secondFragment
}
R.id.navigation_third -> {
fm.beginTransaction().hide(active).show(thirdFragment).commit()
active = thirdFragment
}
}
true
}
And remove these lines in your nav_host_fragment:
app:defaultNavHost="true"
app:navGraph="#navigation/mobile_navigation"

you can use jetpack navigation for simple bottombar navigation
Simple Bottom Navigation with Jetpack Navigation:
Let’s start by including the Jetpack Navigation library in your apps by adding these lines in app’s build.gradle file:
def nav_version = "2.1.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
We start by creating a simple bottom navigation flow first. For that, you need to do first add NavHostFragment in your single activity layout file. Add this in the activity_main.xml file inside the FrameLayout tag.
<fragment
android:id="#+id/fragNavHost"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/bottom_nav_graph" />
You will see an error saying “Cannot resolve symbol #navigation/bottom_nav_graph .”
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/bottom_nav_graph.xml"
app:startDestination="#id/homeFragment2">
<fragment
android:id="#+id/homeFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.HomeFragment"
android:label="fragment_home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/searchFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.SearchFragment"
android:label="fragment_search"
tools:layout="#layout/fragment_search" />
<fragment
android:id="#+id/notificationsFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.NotificationsFragment"
android:label="fragment_notifications"
tools:layout="#layout/fragment_notifications" />
<fragment
android:id="#+id/profileFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.ProfileFragment"
android:label="fragment_profile"
tools:layout="#layout/fragment_profile" />
</navigation>
Its time for add some code in our activity class. Open MainActivity.kt file, and create a method setupViews() in it. Call this in onCreate() of the activity. Add these lines in the setupVeiws() method.
fun setupViews()
{
// Finding the Navigation Controller
var navController = findNavController(R.id.fragNavHost)
// Setting Navigation Controller with the BottomNavigationView
bottomNavView.setupWithNavController(navController)
// Setting Up ActionBar with Navigation Controller
// Pass the IDs of top-level destinations in AppBarConfiguration
var appBarConfiguration = AppBarConfiguration(
topLevelDestinationIds = setOf (
R.id.homeFragment,
R.id.searchFragment,
R.id.notificationsFragment,
R.id.profileFragment
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
}

When I tried #Kasım Özdemir 's answer, Each time the activity is launched, There was an initial ripple effect on the first item in bottom navigation view.(because I am using material bottom navigation view, which has a default ripple effect. And also UI was not visible when I tap for the first time, but I think that was because I was using different method than #Kasım Özdemir to change my active fragment when Item is clicked.
I didn't want to start my activity with first item, but with middle item in navigation view, which is "FragmentTwo" in below case. So the ripple effect was quite irrelevant.
So, I just attached the fragment instead of adding it and then hiding it, and now there is no ripple, Here is how code looks in Kotlin...
val firstFragment: Fragment = FragmentOne()
val middleFragment: Fragment = FragmentTwo()
val thirdFragment: Fragment = FragmentThree()
var fragment: Fragment? = null
var bnv: BottomNavigationView? = null
bnv = findViewById(R.id.bottom_navigation)
bnv!!.selectedItemId = R.id.middle_page
//fragments attached other than the fragment linked with middle item
supportFragmentManager.beginTransaction().attach(firstFragment).commit()
supportFragmentManager.beginTransaction().attach(thirdFragment).commit()
supportFragmentManager.beginTransaction().add(R.id.activity_main_container, middleFragment())
.commit()
bnv!!.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.first_page -> fragment = firstFragment
R.id.middle_page -> fragment = middleFragment
R.id.third_page -> fragment = thirdFragment
}
if (item.isChecked){
false
}
else {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out).replace(
R.id.activity_main_container,
fragment!!
).commit()
true
}
}
So, using attach can also work fine instead of adding and then hiding it... for more information you can check this answer
(If you haven't already, Try using Navigation Component for bottom navigation, like this. It is more handy in some case, as it handles default backStack management by itself.)

Related

How to display my Navigation bar contents without interruption?

I built a customizable navigation drawer from scratch(didn't make use of the default drawer provided by Android Studio). In my weather app's navigation bar menu https://i.stack.imgur.com/SIjdx.jpg, whenever I select an option on the menu(say settings), it displays the contents of the option along with the bottom navigation view and my Activity's Toolbar contents which comprises of the nav hamburger icon, the edittext and the search button(the activity hosting my 3 fragments) which spoils the app and makes it look very ugly i.e. https://i.stack.imgur.com/gxj5n.jpg (From that screenshot, the entire content should be empty if implemented well). The case is the same for the other bar menu options. All I want is an empty space to work on, I want the app to only display the navigation bar contents without the rest. Example; https://i.stack.imgur.com/3Jtga.png Please how should I do this?
The view of the Navigation Menu is controlled by this code(on line 185):
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.settings_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Settings()).commit();
break;
case R.id.ads_upgrade_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Upgrade()).commit();
break;
case R.id.privacy_policy_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Privacy_Policy()).commit();
break;
}
drawer.closeDrawer(GravityCompat.START);
return true;
}
"fragment" there represents that I'm currently using my fragment's container view on my activity to display the Nav menu contents which I know is wrong for sure, so what should I use in replace? I lack strong experience as it's my first time building an app and I've tirelessly spent 3 hours on my own trying to figure out the issue which proved abortive.
Here is my Activity code:
public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout drawer;
// Last update time, click sound, search button, search panel.
TextView timeField;
MediaPlayer player;
ImageView Search;
EditText textfield;
// For scheduling background image change(using constraint layout, start counting from dubai, down to statue of liberty.
ConstraintLayout constraintLayout;
public static int count = 0;
int[] drawable = new int[]{R.drawable.dubai, R.drawable.norway, R.drawable.eiffel_tower, R.drawable.hong_kong, R.drawable.statue_of_liberty,
R.drawable.beijing, R.drawable.chicago, R.drawable.colombia, R.drawable.vienna,R.drawable.tokyo};
Timer _t;
private WeatherDataViewModel viewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// use home activity layout.
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Allow activity to make use of the toolbar
drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);
// Trigger action to open & close navigation drawer
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar
, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
timeField = findViewById(R.id.textView9);
Search = findViewById(R.id.imageView4);
textfield = findViewById(R.id.textfield);
// find the id's of specific variables.
BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
// host 3 fragments along with bottom navigation.
final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
assert navHostFragment != null;
final NavController navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(bottomNavigationView, navController);
// Make hourly & daily tab unusable
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
}
return false;
});
navController.addOnDestinationChangedListener((controller, destination, arguments) -> navController.popBackStack(destination.getId(), false));
// For scheduling background image change
constraintLayout = findViewById(R.id.layout);
constraintLayout.setBackgroundResource(R.drawable.dubai);
_t = new Timer();
_t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
// run on ui thread
runOnUiThread(() -> {
if (count < drawable.length) {
constraintLayout.setBackgroundResource(drawable[count]);
count = (count + 1) % drawable.length;
}
});
}
}, 5000, 5000);
Search.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// make click sound when search button is clicked.
player = MediaPlayer.create(HomeActivity.this, R.raw.click);
player.start();
getWeatherData(textfield.getText().toString().trim());
// make use of some fragment's data
Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
if (currentFragment instanceof FirstFragment) {
FirstFragment firstFragment = (FirstFragment) currentFragment;
firstFragment.getWeatherData(textfield.getText().toString().trim());
} else if (currentFragment instanceof SecondFragment) {
SecondFragment secondFragment = (SecondFragment) currentFragment;
secondFragment.getWeatherData(textfield.getText().toString().trim());
} else if (currentFragment instanceof ThirdFragment) {
ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
thirdFragment.getWeatherData(textfield.getText().toString().trim());
}
}
private void getWeatherData(String name) {
ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
Call<Example> call = apiInterface.getWeatherData(name);
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(#NonNull Call<Example> call, #NonNull Response<Example> response) {
try {
assert response.body() != null;
timeField.setVisibility(View.VISIBLE);
timeField.setText("First Updated:" + " " + response.body().getDt());
} catch (Exception e) {
timeField.setVisibility(View.GONE);
timeField.setText("First Updated: Unknown");
Log.e("TAG", "No City found");
Toast.makeText(HomeActivity.this, "No City found", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(#NotNull Call<Example> call, #NotNull Throwable t) {
t.printStackTrace();
}
});
}
});
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.settings_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Settings()).commit();
break;
case R.id.ads_upgrade_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Upgrade()).commit();
break;
case R.id.privacy_policy_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Privacy_Policy()).commit();
break;
}
drawer.closeDrawer(GravityCompat.START);
return true;
}
#Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
// Open/close drawer animation
}
}
}
In case you require any other code to look into the issue, please let me know. I'm just trying to avoid posting too much
EDIT:
My old bottomtabs nav graph:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/my_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.viz.lightweatherforecast.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
<fragment
android:id="#+id/secondFragment"
android:name="com.viz.lightpreciseweatherforecast.SecondFragment"
android:label="fragment_second"
tools:layout="#layout/fragment_second" />
<fragment
android:id="#+id/thirdFragment"
android:name="com.viz.lightpreciseweatherforecast.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third" />
</navigation>
My new nav bar graph:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/bar_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.viz.lightweatherforecast.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
<fragment
android:id="#+id/settings_id"
android:name="com.viz.lightweatherforecast.Settings"
android:label="#string/settings"
tools:layout="#layout/settings" />
<fragment
android:id="#+id/ads_upgrade_id"
android:name="com.viz.lightweatherforecast.Upgrade"
android:label="#string/upgrade_to_remove_ads"
tools:layout="#layout/upgrade" />
<fragment
android:id="#+id/privacy_policy_id"
android:name="com.viz.lightweatherforecast.Privacy_Policy"
android:label="#string/privacy_policy"
tools:layout="#layout/privacy_policy"/>
</navigation>
You are using navigation architecture components, so the navController is the one that should control fragment transactions, you are doing that right with BottomNavigationView.
But within the navDrawer you are doing the transaction through the supportFragmentManager which should be done through the navController instead as both handle the navigation differently.
whenever I select an option on the menu(say settings), it displays the contents of the option along with the bottom navigation view
That is because the BottomNavView is a part of the activity, and you need to move it to a fragment; this requires to change the navigation design of your app; to do that change your app navigation like the below:
Main navigation:
<navigation
..... >
<fragment
android:name="......HomeFragment"/>
<fragment
android:name="......SettingFragment"/>
<fragment
android:name="......AdsUpgradeFragment"/>
<fragment
android:name="......PrivacyPolicyFragment"/>
</navigation>
The HomeFragment is the fragment that should hold the BottomNaviagtionView instead of the activity; and when you navigate to the SettingFragment, the navConroller will replace the entire fragment in the navHostFragment, and therefore the BottomNaviagtionView won't be shown.
my Activity's Toolbar contents which comprises of the nav hamburger
icon, the edittext and the search button(the activity hosting my 3
fragments) which spoils the app and makes it look very ugly
Unlike the BottomNaviagtionView, you can't do that with your toolBar that is used as the supportActionBar, because setting supportActionBar more than once in order to change its look; will duplicates it; so you have to accept a single toolbar; but instead you can hide/show the layout that holds the search button & the EditText whenever the destination changes:
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
LinearLayout searchBar = findViewById(R.id.searchbar); // change searchbar according to the layout id that holds the search button and the EditText
if (destination.getId() == R.id.nav_home) {
searchBar.setVisibility(View.VISIBLE);
} else {
searchBar.setVisibility(View.GONE);
}
});
and yes they currently exit the app when clicking back
To exit the app whenever, the bottom back button is pressed in any fragment use OnBackPressedDispatcher() within onCreateView() of those fragment (in your case SettingFragment, PrivacyPolicyFragment, & AdsUpgradeFragment):
And make sure that appBarConfiguration doesn't reference those fragments so, that the UP button can be shown instead of the burger.
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
// Exit the app when back is pressed
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
requireActivity().finishAndRemoveTask();
else requireActivity().finish();
}
});
Also, make sure that your setup the DrawerLayout & its navView in the navController with:
NavigationView navView = findViewById(....);
appBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home) // remove up button from all these fragments >> Keep the up/back button in R.id.settings_id, R.id.settings_id, ads_upgrade_id, privacy_policy_id
.setOpenableLayout(drawer)
.build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
And to make the Home fragment hidden by default in the navDrawer:
navView.getMenu().findItem(R.id.nav_home).setVisible(false); // adjust R.id.nav_home to yours
UPDATE
I currently have a single nav graph. The firstFragment represents the
today, 2nd - hourly and 3rd
I'm using is for my other weather tabs but the one you're suggesting
is for the navbar menu, should I replace yours with mine or just
create a new one for your suggestion?
You should use two navGraphs, the first is for the main navigation which I'd suggested; and the second is for the BottomNavigationView navigation which you already use; that is because we transferred the BottomNavigationView from the activity layout to the main/home fragment layout; and it' recommended the BottomNavigationView should have a separate navGraph;
So, you now need two FragmentContainerView; the first is in the activity layout which reference the navGraph provided in this answer, and the second is in the home fragment layout that references your original navGraph of the BottomNavigationView.
Sample:
I think I understood your problem very well (if I am not wrong). Thing is that what behavior you are getting right now is the normal scenario. You are using the same host activity to host both Navigation Drawer and Bottom Navigation fragments thus when you tried to navigate to another fragment from the same host the presence of the host's direct child view Bottom NavBar is showing. I think you can solve this problem in a few different but pretty simple logical ways.
(Not recommended) Using a different Activity for the Settings page. Just like startActivity(this, <some intent>). But in this way, you will end up creating lots of individual activities.
(Recommended) Using a common Navigation Host activity for navigating to independent pages/fragments. Like: Settings, Contact Us, etc. You can add different fragments into the common_nav_graph and set some actions with or without arguments for navigation. Just add the common_nav_graph as a nested graph inside your current one and set a simple argumented action. The arguments will help you to navigate desired pages/fragments without showing the bottom navigation bar.
Simply hide Bottom navigation when navigating to some fragments from your drawer.

ViewPager2 not working properly with Fragments and click events

I just wanna know if I'm doing something wrong since I'm kinda new to all this.
If there is anything else that you'll like me to add just let me know.
This is the repo branch where I'm trying to implement the ViewPager if you wanna see all the code.
Context
So I have 4 Categories represented with Fragments, each of this categories holds an ArrayList of items that each has a onItemClickListener that should reproduce some audio.
I'm trying to display the Fragments with a ViewPager but the problem is that when I scroll from a Fragment to another, then come back to the already created Fragment, it doesnt register the touch event, nothing happens, not even an error nor exception.
If I go to a newly created Fragment the touch works just fine.
Also, after switching back to an already created Fragment if I scroll even just a little bit to another Fragment and comeback or through the ArrayList of that Fragment for some reason it starts to recognize the touch in the ArrayList items again.
Similar questions that didn't really help
Fragments in ViewPager2 does not respond to clicks if scroll position is 0
ViewPager2 conflicting with SwipeRefreshLayout
Android ViewPager2 with fragment containing a recyclerview not scrolling
What I've tried
I tried to use a coordinatorlayout wrapping the ViewPager2 but there is no difference
I've been reading some of the official viewPager2 examples that are written in Kotlin but none of them seem to have a similar situation (also it's hard for me to read Kotlin code)
Code Snippets
word_list.xml:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/tan_background" />
activity_main.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"/>
</FrameLayout>
This is one of the Fragments, the other three are basically the same, just the items in the arrayList change and some other minor things:
// ...Skipped some irrelevant code...
public class NumbersFragment extends Fragment {
private ArrayList<Word> mWords;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.word_list, container, false);
mWords = new ArrayList<>();
// ...Add all the items to the list...
// Make the adapter for the word items
WordAdapter adapter = new WordAdapter(getActivity(), mWords, R.color.category_numbers);
// Find the root view of the list
ListView listView = rootView.findViewById(R.id.root_list_view);
// Add adapter to the root list view
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("NumbersFragment", "CLICKED");
}
}
});
return rootView;
}
#Override
public void onPause() {
super.onPause();
Log.d("NumbersFragment", "Fragment paused");
}
}
This is the Category adapter, it manages the fragments:
public class CategoryAdapter extends FragmentStateAdapter {
private static final int NUM_CATEGORIES = 4;
// Required public constructor
public CategoryAdapter(#NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
#NonNull
#Override
public Fragment createFragment(int position) {
// Depending on which page the user is in,
// create a fragment of the corresponding category
switch (position) {
case 0:
return new NumbersFragment();
case 1:
return new FamilyFragment();
case 2:
return new ColorsFragment();
default:
return new PhrasesFragment();
}
}
#Override
public int getItemCount() {
return NUM_CATEGORIES;
}
}
And this is my MainActivity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the content of the activity to use the activity_main.xml layout file
setContentView(R.layout.activity_main);
// Find the view pager that will allow the user to swipe between fragments
ViewPager2 viewPager = findViewById(R.id.viewpager);
// Create an adapter that knows which fragment should be shown on each page
CategoryAdapter adapter = new CategoryAdapter(this);
//or CategoryAdapter adapter = new CategoryAdapter(getSupportFragmentManager(), getLifecycle());
// Set the adapter into the view pager
viewPager.setAdapter(adapter);
}
}
add this in your MainActivity viewPager.setOffscreenPageLimit(3); after creating viewpager
It’s because the ViewPager has a default offscreen limit of 1 ,and ViewPager2 has a default offscreen limit of 0.
In ViewPager2 when you switch tabs the previous tab will be automatically refreshed.
in ViewPager if you have 3 tabs or more when you switch to 3rd tab automatically first one will be destroyed and when you goes to 1st tab it will be recreated.
viewPager.setOffscreenPageLimit(3); from this line when you switch to a tab,the previous 3 tabs will be preloaded and next 3 tabs will be preloaded so nothing will be refreshed.

How to use the navigation drawer menu without loading it each time?

This is more of a Java question (having some trouble understanding how should the inheritance be set). I'm trying to add a side menu to my application (which works ok). The class signature of my menu activity is:
public class MenuActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener
Once the user logged in, the first window he see is the main dashboard. The dashboard activity extends the menu activity in order to have the side menu:
public class DashboardActivity extends MenuActivity
For navigating to other activities, I implemented the onNavigationItemSelected method to select the intent to load (The method is located in the MenuActivity):
#Override
public boolean onOptionsItemSelected(#NonNull MenuItem item) {
if (toggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Intent intent = null;
switch (item.getItemId()) {
case R.id.lay_dashboard:
intent = new Intent(this, DashboardActivity.class);
//intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
break;
case R.id.lay_settings:
intent = new Intent(this, SettingsActivity.class);
//intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
break;
case R.id.lay_contacts:
startActivity(new Intent(this, ContactsActivity.class));
break;
case R.id.lay_about:
startActivity(new Intent(this, AboutActivity.class));
break;
case R.id.lay_logout:
mAuth.signOut();
intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
break;
}
if (intent != null) {
startActivity(intent);
}
drawerLayout.closeDrawer(GravityCompat.START);
return true;
}
As you can see I commented the intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); as part of debugging the issue. I want the navigation process not to start a new screen, rather move to it.
Also the OnCreate method in the MenuActivity looks like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
}
drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.lay_nav_view);
toggle = new ActionBarDrawerToggle(this, drawerLayout,
R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(toggle);
toggle.syncState();
navigationView.setNavigationItemSelectedListener(this);
mAuth = FirebaseAuth.getInstance();
logged_user = mAuth.getCurrentUser();
if (logged_user != null) {
// Get data from firebase in order to set the avatar
// and user name in the menu
}
}
protected void setLayoutView(int layout) {
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
View contentView = inflater.inflate(layout, null, false);
drawerLayout.addView(contentView, 0);
}
}
Each one of the activitise extends the MenuActivity and does the following in their onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setLayoutView(R.layout.activity_about); // My method which loads UI
// other code
}
So, as you can see, each one of the activities is extending the MenuActivity. As I understand, this means that everytime I navigate from one activity to another, the OnCreate of MenuActivity is being executed again. I fetch data from the Firebase in that method in order to set the username and the avatar in the top of the menu. So everytime I navigate from one activity to another, it will fetch again and again. How can I make the menu to load only once in a logged mode ? Also, thanks to all of you that have read this topic (I know it's long).
EDIT: To make it more clear, I'm adding the hierarchy of my code:
Maybe the solution for this issue is to make the MenuActivity a singleton class?
All I want to have a menu on each one of the activities and fetching the data only once.
Hello you could use NavigationDrawer with fragments. That will be the most optimal solution and easy, You could also use Navigation Component from Jetpack to change the destinations.
you could use something like this,the following layout uses a DrawerLayout with two child views: a NavHostFragment to contain the main content and a NavigationView for the contents of the navigation drawer.
<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- Layout to contain contents of main body of screen (drawer will slide over this) -->
<fragment
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="#+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
Next, connect the DrawerLayout to your navigation graph by passing it to AppBarConfiguration, as shown in the following example:
val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
Next, in your main activity class, call setupWithNavController() from your main activity's onCreate() method, as shown below:
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)
...
val navController = findNavController(R.id.nav_host_fragment)
findViewById<NavigationView>(R.id.nav_view)
.setupWithNavController(navController)
}
Thats it , then you can use the navContoller to navigate to any fragment,
and rest of what you need is a navigation graph and actions set on destinations to navigate between fragments this way :
navController.navigate(R.id.actionSettingFragment_to_ContactsFrament)
Hope this is kinda helpful. I have a blog written on this so please check it if it is useful to you.
There's more than one way to resolve your issue. I'm going to provide different approaches towards a solution.
Repository
To reuse the data you fetch from Firebase, it needs to be persisted or cached within your application. In general you'd implement an repository abstraction around the Firebase. This repository could be a singleton and just cache the data in memory directly. You only have to make sure to cleanup the cache when necessary. The MenuActivity mustn't be a singlton.
Intent extras
Instead of storing the data in memory, you could provide it as Intent extras. Whenever the data is present, just skip the loading and reuse the data provided.
Fragments
Instead of a stack of activities, you could use fragments. So you'll have a single activity and just replace the fragments within it. With this approach you'll just define a single drawer layout you'll automatically use with every fragment. This approach enables you to use Navigation components as well.

Collapsing Toolbar only for one Fragment in Navigation View

The Problem
I have a navigation drawer with different fragments. There is a default toolbar every Fragment should use, except of one Fragment which needs a collapsing Toolbar.
My Question
How can I switch between the toolbars for the fragments ?
It seems you want to achieve something like this.
I have made an activity with common toolbar. when switching to the collapsing toolbar fragment I've made the toolbar transparent and fragment's toolbar takes over. The toolbar's color remains the same on switching to other fragments.
This allows you to manage complete collapsing toolbar's layout structure in xml and logic remains in Fragment.
Hope this will help. Refer the gif linked.
Gist for gif
The best solution that I found to easily collapse, lock it(keep it in collapsed mode) and unlock the collapsingToolbar.
private void collapseAppBar() {
// Collapse the AppBarLayout with animation
mAppBarLayout.setExpanded(false, true);
}
private void lockAppBar() {
/* Disable the nestedScrolling to disable expanding the
appBar with dragging the nestedScrollView below it */
ViewCompat.setNestedScrollingEnabled(nestedScrollView, false);
/* But still appBar is expandable with dragging the appBar itself
and below code disables that too
*/
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return false;
}
});
}
private void unLockAppBar() {
ViewCompat.setNestedScrollingEnabled(nestedScrollView, true);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
if (behavior != null) {
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return true;
}
});
}
}
And I use these functions in this way:
Fragment fragment = null;
Class fragmentClass;
switch (menuItem.getItemId()) {
case R.id.fragment1:
unLockAppBar();
fragmentClass = first_Fragment.class;
break;
case R.id.fragment2:
collapseAppBar();
lockAppBar();
fragmentClass = second_Fragment.class;
break;
case R.id.fragment3:
collapseAppBar();
lockAppBar();
fragmentClass = third_Fragment.class;
break;
You can easily get the Toolbar from your Fragment and then modify or change some property of that Toolbar inside the Fragment.
To get the Toolbar from your Activity you might consider using this.
Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
Now you need to make the changes on the Toolbar in the onResume function and then undo the changes each time you return from the Fragment inside onStop function. Otherwise the changes made in the Fragment will be carried on to other fragments as well when switched to other Fragment from the navigation drawer.
But in your case, I would recommend each Fragment should have their Toolbar so that it doesn't conflict with each other and can be modified as you need. And yes, remove the Toolbar from your Activity.
So add the Toolbar in the layout of your Fragment like this.
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimaryDark"/>
Then find it in the Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);
Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
// Modify your Toolbar here.
// ...
// For example.
// toolbar.setBackground(R.color.red);
// Create home button
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.setSupportActionBar(toolbar);
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
And Override the onOptionsItemSelected function.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case android.R.id.home:
getActivity().onBackPressed();
}
return super.onOptionsItemSelected(item);
}
I'm using Jetpack's Navigation components with single Activity and different Fragments in my app.
Some Fragments are accessible from bottom navigation (and have Toolbar from Activity). Some others are "special" Fragments and have their own Collapsible Toolbar.
To achieve this, I'm hiding Toolbar from Activity in "special" Fragments with this code in Activity:
// Handle toolbar changes in different Fragments
val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.my_special_fragment_with_collapsible_toolbar -> {
binding.toolbarMain.visibility = View.GONE
}
else -> {
binding.toolbarMain.visibility = View.VISIBLE
}
}
}
The recommend practice is to use toolbars in fragments instead of a common toolbar in activity. That way you can control the looks and behaviour of toolbar in fragment. Refer https://developer.android.com/guide/navigation/navigation-ui#support_app_bar_variations

Android getListView() in fragment error

I keep having an issue with my android app where it is crashing with the following error when swiping between tabs:
09-16 16:19:27.142 4750-4750/com.khackett.runmate E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.khackett.runmate, PID: 4750
java.lang.IllegalStateException: Content view not yet created
at android.support.v4.app.ListFragment.ensureList(ListFragment.java:328)
at android.support.v4.app.ListFragment.getListView(ListFragment.java:222)
at com.khackett.runmate.ui.MyRunsFragment$1.done(MyRunsFragment.java:167)
at com.khackett.runmate.ui.MyRunsFragment$1.done(MyRunsFragment.java:135)
at com.parse.ParseTaskUtils$2$1.run(ParseTaskUtils.java:115)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
This is the MyRunsFragment:
public class MyRunsFragment extends ListFragment {
protected SwipeRefreshLayout mSwipeRefreshLayout;
// member variable to store the list of routes the user has accepted
protected List<ParseObject> mAcceptedRoutes;
private int MY_STATUS_CODE = 1111;
// Default constructor for MyRunsFragment
public MyRunsFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_my_runs, container, false);
// Set SwipeRefreshLayout component
mSwipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipeRefreshLayout);
// Set the onRefreshListener
mSwipeRefreshLayout.setOnRefreshListener(mOnRefreshListener);
mSwipeRefreshLayout.setColorSchemeResources(
R.color.swipeRefresh1,
R.color.swipeRefresh2,
R.color.swipeRefresh3,
R.color.swipeRefresh4);
return rootView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// Retrieve the accepted routes from the Parse backend
retrieveAcceptedRoutes();
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
// create the message object which is set to the message at the current position
ParseObject route = mAcceptedRoutes.get(position);
// String messageType = message.getString(ParseConstants.KEY_FILE_TYPE);
JSONArray parseList = route.getJSONArray(ParseConstants.KEY_LATLNG_POINTS);
JSONArray parseListBounds = route.getJSONArray(ParseConstants.KEY_LATLNG_BOUNDARY_POINTS);
String objectId = route.getObjectId();
String routeName = route.getString(ParseConstants.KEY_ROUTE_NAME);
// JSONArray ids = route.getJSONArray(ParseConstants.KEY_RECIPIENT_IDS);
// Start a map activity to display the route
Intent intent = new Intent(getActivity(), MapsActivityTrackRun.class);
intent.putExtra("parseLatLngList", parseList.toString());
intent.putExtra("parseLatLngBoundsList", parseListBounds.toString());
intent.putExtra("myRunsObjectId", objectId);
intent.putExtra("myRunsRouteName", routeName);
// Start the MapsActivityDisplayRoute activity
startActivityForResult(intent, MY_STATUS_CODE);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
private void retrieveAcceptedRoutes() {
// query the routes class/table in parse
// get messages where the logged in user ID is in the list of the recipient ID's (we only want to retrieve the messages sent to us)
// querying the message class is similar to how we have been querying users
ParseQuery<ParseObject> queryRoute = new ParseQuery<ParseObject>(ParseConstants.CLASS_ROUTES);
// use the 'where' clause to search through the messages to find where our user ID is one of the recipients
queryRoute.whereEqualTo(ParseConstants.KEY_ACCEPTED_RECIPIENT_IDS, ParseUser.getCurrentUser().getObjectId());
// order results so that most recent message are at the top of the inbox
queryRoute.addDescendingOrder(ParseConstants.KEY_CREATED_AT);
// query is ready - run it
queryRoute.findInBackground(new FindCallback<ParseObject>() {
// When the retrieval is done from the Parse query, the done() callback method is called
#Override
public void done(List<ParseObject> routes, ParseException e) {
// dismiss the progress indicator here
// getActivity().setProgressBarIndeterminateVisibility(false);
// End refreshing once routes are retrieved
// done() is called from onResume() and the OnRefreshListener
// Need to check that its called from the the OnRefreshListener before ending it
if (mSwipeRefreshLayout.isRefreshing()) {
mSwipeRefreshLayout.setRefreshing(false);
}
// the list being returned is a list of routes
if (e == null) {
// successful - routes found. They are stored as a list in messages
mAcceptedRoutes = routes;
// adapt this data for the list view, showing the senders name
// create an array of strings to store the usernames and set the size equal to that of the list returned
String[] usernames = new String[mAcceptedRoutes.size()];
// enhanced for loop to go through the list of users and create an array of usernames
int i = 0;
for (ParseObject message : mAcceptedRoutes) {
// get the specific key
usernames[i] = message.getString(ParseConstants.KEY_SENDER_NAME);
i++;
}
// Create the adapter once and update its state on each refresh
if (getListView().getAdapter() == null) {
// the above adapter code is now replaced with the following line
RouteMessageAdapter adapter = new RouteMessageAdapter(getListView().getContext(), mAcceptedRoutes);
// Force a refresh of the list once data has changed
adapter.notifyDataSetChanged();
// need to call setListAdapter for this activity. This method is specifically from the ListActivity class
setListAdapter(adapter);
} else {
// refill the adapter
// cast it to RouteMessageAdapter
((RouteMessageAdapter) getListView().getAdapter()).refill(mAcceptedRoutes);
}
}
}
});
}
protected SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
// When list is swiped down to refresh, retrieve the users runs from the Parse backend
retrieveAcceptedRoutes();
}
};
}
And the fragment_my_runs layout file:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity$PlaceholderFragment">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true">
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:clipToPadding="false"
android:paddingBottom="#dimen/inbox_vertical_margin"/>
</android.support.v4.widget.SwipeRefreshLayout>
<TextView
android:id="#android:id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/empty_inbox_label"
android:textSize="#dimen/default_text_size"/>
</RelativeLayout>
The TabFragmentContainer
public class TabFragmentContainer extends Fragment {
// Create the FragmentPagerAdapter that will provide and manage tabs for each section.
public static MyFragmentPagerAdapter myFragmentPagerAdapter;
public static TabLayout tabLayout;
// The ViewPager is a layout widget in which each child view is a separate tab in the layout.
// It will host the section contents.
public static ViewPager viewPager;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate tab_layout_fragment_container view and setup views for the TabLayout and ViewPager items.
View view = inflater.inflate(R.layout.tab_layout_fragment_container, null);
tabLayout = (TabLayout) view.findViewById(R.id.tabs);
// Set up the ViewPager with the sections adapter.
viewPager = (ViewPager) view.findViewById(R.id.viewpager);
// Instantiate the adapter that will return a fragment for each of the three sections of the main activity
myFragmentPagerAdapter = new MyFragmentPagerAdapter(getActivity(), getChildFragmentManager());
// Set up the adapter for the ViewPager
viewPager.setAdapter(myFragmentPagerAdapter);
// Runnable() method required to implement setupWithViewPager() method
tabLayout.post(new Runnable() {
#Override
public void run() {
tabLayout.setupWithViewPager(viewPager);
viewPager.setCurrentItem(1, false);
// tabLayout.getTabAt(1).select();
}
});
// Return the created View
return view;
}
}
The FragmentPagerAdapter:
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
// The context to be passed in when the adapter is created.
private Context mContext;
// The number of tabs in the layout.
public static int numberOfTabs = 3;
/**
* Default constructor that accepts a FragmentManager parameter to add or remove fragments.
*
* #param context the context from the activity using the adapter.
* #param fragmentManager the FragmentManager for managing Fragments inside of the TabFragmentContainer.
*/
public MyFragmentPagerAdapter(Context context, FragmentManager fragmentManager) {
super(fragmentManager);
mContext = context;
}
/**
* Method to return the relevant fragment for the selected tab.
*/
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new MyRunsFragment();
case 1:
return new InboxRouteFragment();
case 2:
return new FriendsFragment();
}
return null;
}
/**
* Method that gets the number of tabs in the layout.
*
* #return the number of tabs in the layout.
*/
#Override
public int getCount() {
return numberOfTabs;
}
/**
* Method that returns the title of each tab in the layout.
*/
#Override
public CharSequence getPageTitle(int position) {
Locale locale = Locale.getDefault();
switch (position) {
case 0:
return mContext.getString(R.string.title_section1).toUpperCase(locale);
case 1:
return mContext.getString(R.string.title_section2).toUpperCase(locale);
case 2:
return mContext.getString(R.string.title_section3).toUpperCase(locale);
}
return null;
}
}
The tab_layout_fragment_container file that contains the ViewPager widget:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/ColorPrimaryPurple"
app:tabGravity="fill"
app:tabIndicatorColor="#color/ColorPrimaryPurple"
app:tabMode="fixed"
app:tabSelectedTextColor="#color/textColorPrimary"
app:tabTextColor="#color/pressedPurpleButton">
</android.support.design.widget.TabLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
</LinearLayout>
The onCreate() method in my MainActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialise the DrawerLayout and NavigationView views.
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
mNavigationView = (NavigationView) findViewById(R.id.navigationDrawerMenu);
// Inflate the first fragment to be displayed when logged into the app.
mFragmentManager = getSupportFragmentManager();
mFragmentTransaction = mFragmentManager.beginTransaction();
mFragmentTransaction.replace(R.id.containerView, new TabFragmentContainer()).commit();
// Setup click events on the NavigationView items.
// When an item is selected, replace the tab fragment container with the requested fragment.
mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
mDrawerLayout.closeDrawers();
if (menuItem.getItemId() == R.id.navItemHome) {
FragmentTransaction tabFragmentContainer = mFragmentManager.beginTransaction();
tabFragmentContainer.replace(R.id.containerView, new TabFragmentContainer()).commit();
}
if (menuItem.getItemId() == R.id.navItemRunHistory) {
FragmentTransaction runHistoryFragment = mFragmentManager.beginTransaction();
runHistoryFragment.replace(R.id.containerView, new RunHistoryFragment()).commit();
}
if (menuItem.getItemId() == R.id.navItemSettings) {
FragmentTransaction settingsFragment = mFragmentManager.beginTransaction();
settingsFragment.replace(R.id.containerView, new SettingsFragment()).commit();
}
if (menuItem.getItemId() == R.id.navItemHelp) {
FragmentTransaction instructionsFragment = mFragmentManager.beginTransaction();
instructionsFragment.replace(R.id.containerView, new InstructionsFragment()).commit();
}
if (menuItem.getItemId() == R.id.navItemMyProfile) {
FragmentTransaction myProfileFragment = mFragmentManager.beginTransaction();
myProfileFragment.replace(R.id.containerView, new MyProfileFragment()).commit();
}
if (menuItem.getItemId() == R.id.navItemLogOut) {
// User has selected log out option. Log user out and return to login screen.
ParseUser.logOut();
navigateToLogin();
}
return false;
}
});
// Set up the Toolbar.
setupToolbar();
}
I have followed other answers here and added the getListView() functionality to the onViewCreated() method but the problem still persists... Can anyone point out where I might be going wrong?
Based on these facts:
The exception is thrown because there is no root view yet when done() calls getListView().
done() is called when the query made by retrieveAcceptedRoutes() gets a response.
retrieveAcceptedRoutes is called in multiple places, including the OnRefreshListener mOnRefreshListener, which is registered as the refresh listener in onCreateView() before there is a root view (that is, before onCreateView() returns).
...it is possible for getListView() to be called before there is a root view.
Try moving these 3 statements from onCreateView() to onViewCreated(), so that way the refresh listener can only be called when there is a root view.
// Set SwipeRefreshLayout component
mSwipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipeRefreshLayout);
// Set the onRefreshListener
mSwipeRefreshLayout.setOnRefreshListener(mOnRefreshListener);
mSwipeRefreshLayout.setColorSchemeResources(
R.color.swipeRefresh1,
R.color.swipeRefresh2,
R.color.swipeRefresh3,
R.color.swipeRefresh4);
onViewCreated is called immediately after onCreateView, but the super.onViewCreated call is missing, perhaps this is root cause of your issue.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); // add this line back in
// Retrieve the accepted routes from the Parse backend
retrieveAcceptedRoutes();
}
I read your question again then I guess that:
Your ListFragment is destroyed while your background task keeps running. So when it's done, your callback would like to update the ListView which is no longer alive.
Actually, viewPager.setOffscreenPageLimit(3); may do the trick, but it's not a good practice. It forces your ViewPager to create and store more Fragments in memory which is not necessary. You can solve this without doing so.
What you should do: one of the following two practice should be fine, or both:
Destroy your task in your onPause or whatever lifecycle method, before your onDestroyView.
Exclude the code where you update your ListView inside your done() method. Make it a local method where you will check your ListView carefully, and there, you should ask your update process to run on UI thread to avoid any threading problem. Make sure to check if your getView() is not null (but not your getListView(), since it throws Exception if getView() returns null).
I recommend you to use both of them to make sure: your view is still useable and you don't waste your resource when running task in invisible fragment. Don't forget that by default, once your fragment is invisible, it is considered to be destroyed (not always, for example ViewPager keep reference of 2 fragments, but keep in mind that case).
remove all these imports:
import com.yourName.runmate.R;
Then resync your gradle and rebuild your project.
Also see here:
"cannot resolve symbol R" in Android Studio
edit
Your first obvious mistake in your Main is
mFragmentManager = getSupportFragmentManager();
should be:
mFragmentManager = getFragmentManager();
or change your Main activity to:
MainActivity extends FragmentActivity to make use of the support fragment manager.
You have a lot of unnecessary code in your question, majority of comments can be removed and imports for the purpose of this question.
What I have come up with is there is no activity, being used. The ListFragment needs to be attached to an Activity or you are trying to call that activity view before it is created.
java.lang.IllegalStateException: Content view not yet created
at android.support.v4.app.ListFragment.ensureList(ListFragment.java:328)
at android.support.v4.app.ListFragment.getListView(ListFragment.java:222)
If you are using Main, then you are not pulling them together well, from what I can see.
Firstly:
Take everything out of your onCreate and onCreateView (for all fragments) except the view inflater.
Place all the extra code into either onViewCreated or onActivityCreated. That way no methods can be called on a null view, as these are called after it is created.
Secondly, you need to sort out your activities and with what you're exactly trying to achieve.
You want a page viewer and a fragmentlist. The pageviewer needs to be associated with an activity, or activity fragment, not a fragment. Otherwise there is no view to attach the pageviewer pages to.
Use a FragmentActivity not a Fragment. To be the activity you run your ListFragment from.
public class TabFragmentContainer extends FragmentActivity {
MyPageAdapter pageAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tab_layout_fragment_container); // change to view layout.
// Instantiate the adapter that will return a fragment for each of the three sections of the main activity
myFragmentPagerAdapter = new MyFragmentPagerAdapter(getFragmentManager(), getFragments());
tabLayout = (TabLayout) view.findViewById(R.id.tabs);
// Set up the ViewPager with the sections adapter.
viewPager = (ViewPager) view.findViewById(R.id.viewpager);
// Set up the adapter for the ViewPager
viewPager.setAdapter(myFragmentPagerAdapter);
}
}
I would suggest putting this into your ListFragment, to ensure your activity is created. You will need to move most of your code from your onCreate methods and put them in onViewCreated or onActivityCreated
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ArrayAdapter adapter = ArrayAdapter.createFromResource(getActivity(), R.layout.my_listview)layout, android.R.layout.simple_list_item_1);
setListAdapter(adapter);
getListView().setOnItemClickListener(this);
}
This code is just a guide, you'll need to tweak it.
Let me know if this helps.
These Q&As are excellent.
Content view not yet created
android Illegal state exception content view not yet create?
Fragment same principles applies to viewpager fragments ViewPager
Try to declare:
viewPager = (ViewPager) view.findViewById(R.id.viewpager);
viewPager.setAdapter(myFragmentPagerAdapter);
myFragmentPagerAdapter = new MyFragmentPagerAdapter(getActivity(), getChildFragmentManager());
before:
tabLayout = (TabLayout) view.findViewById(R.id.tabs);
tabLayout.post(new Runnable() {
#Override
public void run() {
tabLayout.setupWithViewPager(viewPager);
viewPager.setCurrentItem(1, false);
// tabLayout.getTabAt(1).select();
}
});
// Return the created View
return view;

Categories