activity.recreate() inside fragment causes bottom navigation bar to stop working - android

I use 2 fragments inside my MainActivity, one is a preferences fragment and the other (default one) is a home fragment. I wanted to make sure that fragment doesn't get recreated if same item is selected in navigation bar. however now I have 2 problems:
1. If I try to change theme from my preferences the bottom navigation bar will stop working
2. Switching to preferences fragment doesn't change title in action bar but it should (I tested and it worked before implementing this the fragment replacement prevention .
Here are some codes:
MainActivity.kt
class MainActivity : ThemeActivity() {
private val homeFragment: HomeFragment = HomeFragment()
private val settingsFragment: SettingsFragment = SettingsFragment()
private var currentFragment: Fragment? = null
private var activeFragment: Int = R.id.navigation_home
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
savedInstanceState?.let {
activeFragment = it.getInt(CURRENT_FRAGMENT, R.id.navigation_home)
}
val toolbar: MaterialToolbar = findViewById(R.id.home_toolbar)
setSupportActionBar(toolbar)
val prefs = getSharedPreferences("prefs", MODE_PRIVATE)
val firstStart = prefs.getBoolean("firstStart", true)
if (firstStart) {
showSecurityDialog()
}
when (activeFragment) {
R.id.navigation_home -> currentFragment = homeFragment
R.id.navigation_settings -> currentFragment = settingsFragment
}
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.add(R.id.frame_layout, settingsFragment).hide(settingsFragment)
.add(R.id.frame_layout, homeFragment).hide(homeFragment)
.show(currentFragment!!)
.commit()
}
val navView: BottomNavigationView = findViewById(R.id.bottom_nav)
navView.setOnNavigationItemSelectedListener{
setFragments(it.itemId)
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar_menu, menu)
return super .onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.about -> {
val intent = Intent(this, AboutActivity::class.java)
startActivity(intent)
true
}
else -> {
super.onOptionsItemSelected(item)
}
}
private fun showSecurityDialog() {
AlertDialog.Builder(this)
.setTitle("Welcome!")
.setMessage("Before we implement a proper security system to check whether app was modified or not, please be sure that you downloaded manager from vanced.app/github")
.setPositiveButton("close"
) { dialog, _ -> dialog.dismiss() }
.create().show()
val prefs = getSharedPreferences("prefs", MODE_PRIVATE)
val editor = prefs.edit()
editor.putBoolean("firstStart", false)
editor.apply()
}
private fun setFragments(itemId: Int): Boolean {
activeFragment = itemId
when (itemId) {
R.id.navigation_home -> {
if (currentFragment is HomeFragment) {
return false
}
supportFragmentManager
.beginTransaction()
.hide(currentFragment!!)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.show(homeFragment)
.commit()
currentFragment = homeFragment
}
R.id.navigation_settings -> {
if (currentFragment is SettingsFragment) {
return false
}
supportFragmentManager
.beginTransaction()
.hide(currentFragment!!)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.show(settingsFragment)
.commit()
currentFragment = settingsFragment
}
}
return true
}
companion object{
const val CURRENT_FRAGMENT = "current_fragment"
}
}
PreferenceFragment.kt
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
(activity as MainActivity).supportActionBar?.title = getString(R.string.title_settings)
val updateCheck: Preference? = findPreference("update_check")
val themeSwitch: ListPreference? = findPreference("theme_mode")
themeSwitch?.setOnPreferenceChangeListener { _, _ ->
when (themeSwitch.value){
"LIGHT" -> {
activity?.setTheme(R.style.LightTheme_Blue)
activity?.recreate()
}
"DARK" -> {
activity?.setTheme(R.style.DarkTheme_Blue)
activity?.recreate()
}
"FOLLOW" -> {
when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES ->{
activity?.setTheme(R.style.DarkTheme_Blue)
activity?.recreate()
}
Configuration.UI_MODE_NIGHT_NO -> {
activity?.setTheme(R.style.LightTheme_Blue)
activity?.recreate()
}
}
}
else -> {
activity?.setTheme(R.style.LightTheme_Blue)
activity?.recreate()
}
}
true
}
}
}
I tried to lookup on the web but couldn't find anything useful, I'm pretty sure the problem is with the activity recreation but I don't really know how to fix the issue.

Switched to Navigation Components. It's way better than manually making fragment transactions

Related

how to put 2 different Fragments in a BottomNavigationView?

I try to put a BottomNavigationView like this in my main Activity, and I have a recycler view too :
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadFragment(HomeFragment(this))
//import the bottomNavigationView
val navigationView = findViewById<BottomNavigationView>(R.id.barre_nav)
navigationView.setOnNavigationItemSelectedListener {
when(it.itemId) {
R.id.nav_home -> {
loadFragment(HomeFragment(this))
return#setOnNavigationItemSelectedListener true
}
R.id.nav_choose -> {
loadFragment(ChooserFragment(ChooserActivity()))
return#setOnNavigationItemSelectedListener true
}
else -> false
}
}
}
private fun loadFragment(fragment: Fragment) {
val transactionMuscle = supportFragmentManager.beginTransaction()
transactionMuscle.replace(R.id.fragment_container, fragment)
transactionMuscle.addToBackStack(null)
transactionMuscle.commit()
}
But my problem with this code which is fine if I would to put the two view in the same container, but I wouldn't, is : I have a ChooserActivity and I would the second part of my bottomNavigationView to redirect to this page, and not the MainActivity Fragment with the Chooser composant. I would do the same as it do for the main Activity but with the ChooserActivity.
I doesn't know if I am clear, but I Thank you in advance.
EDIT to clarify :
My aim is to with mybottomNavigationView, when we click on the first button, it redirect to the MainActivity, and when we click on the second button, it redirect to the ChooserActivity. Th two must have there own containers. Hopefully it is better...
Solution
try this code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val bottomNavigation: BottomNavigationView = findViewById(R.id.barre_nav)
bottomNavigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
} //onCreate() end here
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.nav_home -> {
val homeFragment = HomeFragment.newInstance()
loadFragment(homeFragment)
return#OnNavigationItemSelectedListener true
}
R.id.nav_choose -> {
val chooserFragment = ChooserFragment.newInstance()
loadFragment(chooserFragment)
return#OnNavigationItemSelectedListener true
}
}
false
}
private fun loadFragment(fragment: Fragment) {
val transactionMuscle = supportFragmentManager.beginTransaction()
transactionMuscle.replace(R.id.fragment_container, fragment)
transactionMuscle.addToBackStack(null)
transactionMuscle.commit()
}
In You Fragments OnCreateView() Method add these lines too :
1.HomeFragment
companion object {
fun newInstance(): HomeFragment = HomeFragment()
}
2.ChooserFragment
companion object {
fun newInstance(): ChooserFragment= ChooserFragment()
}
Hope so it works :)
If you still face any issue , please add it into comments

How to use DropDownPreference to change theme

I want to use a DropDownPreference for my settings page but despite looking all over the internet, there doesn't seem to be any decent tutorial on how to do this. Does anyone know what should go in the onPreferenceChange method? I previously used a RadioButton but now want to use a DropDownPreference for easier implementation and maintenance.
Activity
class SettingsActivity : AppCompatActivity(), FragmentSettings.PreferenceXchangeListener {
private var mCurrentValue: Boolean = false // False is the default value
override fun onCreate(savedInstanceState: Bundle?) {
val mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
mCurrentValue = mSharedPreferences.getBoolean("preference_dark", false)
if (mCurrentValue) {
setTheme(R.style.MyDarkSettingsTheme)
} else {
setTheme(R.style.MyLightSettingsTheme)
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
val viewllSettingsContainer = settings_container
val root = viewllSettingsContainer.rootView
if (mCurrentValue) {
root.setBackgroundColor(Color.BLACK)
} else {
root.setBackgroundColor(Color.WHITE)
}
val settingsFragment = FragmentSettings()
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, settingsFragment)
.commit()
}
override fun onXchange(value:Boolean) {
when {
mCurrentValue != value -> {
mCurrentValue = value
recreate()
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
val intent = parentActivityIntent
intent?.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
onBackPressed()
true
}
else ->
super.onOptionsItemSelected(item)
}
}
}
Fragment
class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.app_preferences)
}
}
I think you're over-complicating it. There is no need to involve the fragment in listening to changes to a preference it won't be handling and passing that back to the activity. You can register a preference change listener for all preferences in your Activity and respond accordingly there.
class SettingsActivity : AppCompatActivity(),
SharedPreferences.OnSharedPreferenceChangeListener {
override fun onCreate(savedInstanceState: Bundle?) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
//...
}
override fun onDestroy() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
//...
super.onDestroy()
}
override fun onSharedPreferenceChanged(_: SharedPreferences, key: String) {
when (key){
"preference_dark" -> recreate()
}
}
}

how to pass data between two fragments destination If I tap a menu in navigation controller?

I am trying to use navigation controller. I have a bottom navigation view. that located on my MainActivity, and it is initiated using the code below on :
class MainActivity : AppCompatActivity() {
lateinit var navController : NavController
lateinit var logoHeaderImageView : ImageView
var toolbarMenu : Menu? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
logoHeaderImageView = findViewById(R.id.header_lakuin_image_view)
navController = Navigation.findNavController(this,R.id.nav_main_host_fragment)
setupBottomNavMenu(navController)
setupActionBar(navController)
}
fun setupBottomNavMenu(navController: NavController) {
NavigationUI.setupWithNavController(bottom_navigation_view,navController)
}
fun setupActionBar(navController: NavController) {
setSupportActionBar(main_activity_toolbar)
main_activity_toolbar.title = ""
val appBarConfiguration = AppBarConfiguration(
setOf(
// set some destination as the top hierarchy destination, to make the up button doesn't show.
R.id.destination_home,
R.id.destination_order,
R.id.destination_favourite,
R.id.destination_cart,
R.id.destination_profile
))
NavigationUI.setupWithNavController(main_activity_toolbar,navController,appBarConfiguration)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_main_toolbar, menu)
toolbarMenu = menu
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
}
}
here is the look of my bottom navigation view:
So I want to pass data from my HomeFragment (destination home) to OderFragment (destination order). I usually using bundle or safeArgs to pass data like the code below:
var bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)
but I don't know where to place that code, If I want to pass data from my HomeFragment to OderFragment
When using NavigationUI.setupWithNavController(bottom_navigation_view,navController) (or, if you're using the navigation-ui-ktx Kotlin extension bottom_navigation_view.setupWithNavController(navController)), you can't pass any custom arguments to destinations - an important part of global navigation is that they always take you to the same screen in the same state.
Generally, you should be holding data like the current amount separately from Navigation arguments - whether it is in a persisted database, SharedPreferences, or some other location that would survive process death, allowing users to continue with what they're doing even after restarting their phone, etc.
However, if you must use Navigation arguments for this, you can set the default argument for your destination ahead of time (i.e., whenever your amount changes):
NavDestination orderDestination = navController.graph.findNode(R.id.destination_order)
orderDestination.addArgument("amount", NavArgument.Builder()
.setType(NavType.FloatType)
.setDefaultValue(amount)
.build())
Afterwards, your BottomNavigationView triggering R.id.destination_order will automatically include that argument, along with your new amount value, by default.
You can use a shared ViewModel between fragments:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}
more information can be found here:
https://developer.android.com/topic/libraries/architecture/viewmodel#sharing
Just like that:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val bundle = Bundle()
bundle.putString("myname","Hussnain")
return when(item.itemId){
R.id.aboutFragment ->{
navController.navigate(R.id.aboutFragment,bundle)
return true
}else -> {
NavigationUI.onNavDestinationSelected(item,navController) || super.onOptionsItemSelected(item)
}
}
}
Navigation:
<fragment android:id="#+id/aboutFragment"
android:name="com.cinderellaman.general.ui.fragments
.AboutFragment"
android:label="about_fragment"
tools:layout="#layout/about_fragment">
<argument android:name="myname" app:argType="string"/>
</fragment>
fragment:
val args: AboutFragmentArgs by navArgs()
name.text = args.myname
Post data:
Fragment fragment = new OderFragment();
Bundle bundle = new Bundle();
bundle.putString("key", "value");
fragment.setArguments(bundle);
FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container, fragment);
transaction.addToBackStack(null);
transaction.commit();
Receive data:
Bundle bundle = this.getArguments();
if (bundle != null) {
myInt = bundle.getString("key");
}
Call Nav contoller from current fragment. And pass data as a bundle
Ex:
First fragment -
val bundle = Bundle()
bundle.putString("crs",crs); //This is the passing parameter
findNavController().navigate(R.id.action_Classes_to_AddUpdateStudents,bundle)
//Amend your parameter as a second argument
Second fragment -
crs = arguments?.getString("crs").toString()
//Retrieve the data as u usually do with an activiy

Kotlin Android Fragment recyclerView and context issue

I would like to create an recyclerView in a fragment, but it shows an error " java.lang.IllegalStateException: recylerView_Main must not be null
at com.gph.bottomnavigation.FragmentMe.onCreateView(FragmentMe.kt:28)"
Question 1) Please kindly help to solve this issue.
Question 2) I created an recyclerView only in a empty project without any fragment, it is working properly.
But the same code is no working in Fragment, it shows error so I change "recylerView_Main.layoutManager = LinearLayoutManager(this)" to "recylerView_Main.layoutManager = LinearLayoutManager(context)"
It shows no error and I can run in simlulator, but when I click the navigation button of the Fragment, the app stops and show this error. Please kindly help to solve it.
Here with the code for FragmentMe.kt:
class FragmentMe : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
recylerView_Main.layoutManager = LinearLayoutManager(context)
recylerView_Main.adapter = Mainadapter()
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_me, container, false)
}
}
Here with the code of MainActivity.kt:
class MainActivity : AppCompatActivity() {
val manager = supportFragmentManager
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
//message.setText(R.string.title_home)
createFragmentQpon()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_dashboard -> {
//message.setText(R.string.title_dashboard)
createFragmentMe()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_notifications -> {
//message.setText(R.string.title_notifications)
createFragmentTools()
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Action Bar
val actionBar = supportActionBar
actionBar!!.setDisplayShowHomeEnabled(true)
actionBar.setBackgroundDrawable(ColorDrawable(Color.parseColor("#00FFFFFF")))
actionBar.setIcon(R.drawable.ic_home_black_24dp)
actionBar.setDisplayShowTitleEnabled(false)
createFragmentQpon()
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
}
fun createFragmentQpon() {
val transaction = manager.beginTransaction()
val fragment = FragmentQpon()
transaction.replace(R.id.fragmentholder,fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createFragmentMe() {
val transaction = manager.beginTransaction()
val fragment = FragmentMe()
transaction.replace(R.id.fragmentholder,fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createFragmentTools() {
val transaction = manager.beginTransaction()
val fragment = FragmentTools()
transaction.replace(R.id.fragmentholder,fragment)
transaction.addToBackStack(null)
transaction.commit()
}
}
Here with the code of Mainadapter.kt:
class Mainadapter: RecyclerView.Adapter<CustomViewHolder>() {
val videolist = listOf("aaa","bbbb","cccc")
override fun getItemCount(): Int {
return 3
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent?.context)
val cellForRow = layoutInflater.inflate(R.layout.tutorial_layout, parent, false)
return CustomViewHolder(cellForRow)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
var videoName = videolist.get(position)
holder.itemView.title.text = videoName
}
}
class CustomViewHolder(v: View): RecyclerView.ViewHolder(v) {
}
Move this code
recylerView_Main.layoutManager = LinearLayoutManager(context)
recylerView_Main.adapter = Mainadapter()
from onCreateView to onActivityCreated
override onActivityCreated and place the above code.
There are two things incorrect in your code :
You are trying to access recyclerView even before inflating the View.
The context of a Fragment is null in onCreateView and is usable in between onAttach and onDetach
recylerView_Main.layoutManager = LinearLayoutManager(this.context)
Try this out, worked fine for me.

How to load fragment on BottomNavigationView depending on selected item?

I have a simple application written in Kotlin that has a BottomNavigationView. The idea is to have a single activity (MainActivity) with a fragment to be loaded for each tab on the BottomNavigationView.
I have already created a fragment I would like to load when a tab is selected (HomeFragment) and am already changing the text under the icons on the BottomNavigationView when the active tab changes.
Now I would like to inflate / load the fragment when I change tabs. How would I go about doing this?
MainActivity.kt:
class MainActivity : AppCompatActivity() {
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_enrollments -> {
message.setText(R.string.title_enrollments)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_timeline -> {
message.setText(R.string.title_timeline)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_home -> {
message.setText(R.string.title_home)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_alerts -> {
message.setText(R.string.title_alerts)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_profile -> {
message.setText(R.string.title_profile)
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
}
override fun onResume() {
super.onResume()
setContentView(R.layout.activity_main)
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.navigation)
bottomNavigationView.selectedItemId = R.id.navigation_home
}
}
Instead of message.setText(R.string.title_enrollments) do
supportFragmentManager.beginTransaction()
.replace(containerViewId, fragmentInstance, "TAG")
.commitAllowingStateLoss()
Or you can use an extension function I use in my code to make it cleaner. Just add this in some .kt file
inline fun FragmentManager.transactStateless(func: FragmentTransaction.() -> Unit) {
val transaction = beginTransaction()
transaction.func()
transaction.commitAllowingStateLoss()
}
and now you can add remove fragment this way:
supportFragmentManager.transactStateless {
replace(containerViewId, fragmentInstance, "TAG")
}

Categories

Resources