I am working on an app with 5 fragments, having one fragment named home fragment.
I want whenever I press the back button on any of the other fragments, it navigates to the home fragment only.
pls, suggest to me how can I do this.? the below code is provided for reference
val expensefragment = expenseFragment()
val notesfragment = notesFragment()
val forumfragment = forumFragment()
val printfragment = printFragment()
val homefragment = homefragment()
setCurrentFragment(homefragment)
val bottomNavigationview = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
bottomNavigationview.setOnNavigationItemSelectedListener { item ->
when(item.itemId) {
(R.id.expensebar)-> setCurrentFragment(expensefragment)
(R.id.notesbar)-> setCurrentFragment(notesfragment)
(R.id.printbar)-> setCurrentFragment(printfragment)
(R.id.forumbar)-> setCurrentFragment(forumfragment)
(R.id.homebar) -> setCurrentFragment(homefragment)
}
true
}
}
private fun setCurrentFragment(fragment: Fragment){
supportFragmentManager.beginTransaction().apply {
replace(R.id.frameLayout,fragment)
commit()
}
}
After replace and before commit call the following function
.addToBackStack(backStateName);
if you don't want to give name of stack , pass null or empty value. This function will create backstack as Intent does.
Related
hello guys,
I'm developing an android app in which I need your help.
in this app, I have a bottom nav bar so I can communicate between 4 fragments A, B, C, and D.
I put the A fragment as the default home fragment.
the problem that, if I want to start my navigationBottom activity (A is the default home fragment after LogInstrong text)
I want to start it with a fragment C for example.
<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/nav"
app:startDestination="#id/account">
Welcome to Stackoverflow :)
Solution
Add these lines in your First Activity(From which you are moving to BottomNav Class)
`Intent intent = new Intent(YourActivity.this, BottomNavAvtivity.class);
intent.putExtra("FromMainActivity", "1");
startActivity(intent);`
Next,In your Second Activity(Class implementing BottomNav) write the below code in onCreate() of BottomNavigation class:
Intent i = getIntent();
String data = i.getStringExtra("FromReservation");
if (data != null && data.contentEquals("1")) {
yourBottomNav.setFragment(yourFragment);
}
This can be achieved with conditional navigation, I would recommend this medium article: https://medium.com/androiddevelopers/navigation-conditional-navigation-e82d7e4905f0
Especially the section around this code snippet (copy-pasted from the medium article) which shows how to change the default screen depending on a shared preferences value:
donutListViewModel.isFirstRun().observe(viewLifecycleOwner) { s ->
if (s == UserPreferencesRepository.Selection.NOT_SELECTED) {
val navController = findNavController()
navController.navigate(
DonutListDirections.actionDonutListToSelectionFragment()
)
}
}
i am new in android, i am working on an app that retrieve data from server using retrofit and kodein and MvvM in kotlin
i set a Navigation drawer in my app and the purpose is that when i click on the item of navigation drawer
new activity opens and in this activity i want show recyclerview
but when new activty opens recyclerview cant set listitem on recycler
i debug my code in my repositories and viewmodel class and i see that they received data
i debug my code in new activty and i see that viewmodel cant receive those data and set a invalid icon next to my code in viewmodel.observe
this is my repository class:
fun getdigitalproduct(): LiveData<List<DigitalProduct>>{
val dpData:MutableLiveData<List<DigitalProduct>> = MutableLiveData<List<DigitalProduct>>()
val apiClient = ApiClient()
val call:Call<List<DigitalProduct>> = apiClient.getClient().create(ApiService::class.java).getdigitalproduct()
call.enqueue(object : Callback<List<DigitalProduct>>{
override fun onResponse(
call: Call<List<DigitalProduct>>,
response: Response<List<DigitalProduct>>
) {
dpData.value = response.body()
}
override fun onFailure(call: Call<List<DigitalProduct>>, t: Throwable) {
dpData.value = null
}
})
return dpData
}
this is for ViewModel
var repoDigitalProduct: LiveData<List<DigitalProduct>> = repositorys.getdigitalproduct()
fun getdigitalproduct(): LiveData<List<DigitalProduct>>{
return repoDigitalProduct
}
this is for new activity:
private fun getDigitalProduct() {
viewModel.getdigitalproduct().observe(this, Observer {
digipro.addAll(it)
})
this is digipro:
var digipro: ArrayList<DigitalProduct> = ArrayList()
and this i use this code in oncreate method in new activity:
viewModel = ViewModelProviders.of(this, factory).get(AllViewModel::class.java)
getDigitalProduct()
setdigitalProductRecycler()
i use viewmodelprovider.of code in Mainactivity too
this is for setdigitalProductRecycler:
private fun setdigitalProductRecycler() {
val digiproRecycler = digital_product_recycler
digiproRecycler.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true)
digiproRecycler.adapter = DigitalProductAdapter(digipro)
}
my codes works in Mainactivity but when i try it in new activity.........
what should i do?
The best way to solve this issue is create your viewmodel in activity or you can use shared viewmodel. So your viewmodel will retain as your activity retain
https://stackoverflow.com/a/52611554/8868638
i put repositorie and viewmodel in companion object then i called viewmodel of MainActivity in that activity
this is for MainActivty:
companion object{
val repositorys = AllRepositorys()
var viewModel: AllViewModel = AllViewModel(repositorys)
}
this is for newActivity:
lateinit var viewModel: AllViewModel
and i put this in oncreate() Method in new activity:
viewModel = MainActivity.viewModel
UPDATE
Now I handle this issue with creating a viewmodel for every fragment or activity. Because, ViewModel is designed to store and manage UI-related data in a lifecycle of Activity or Fragment
I have three fragments: FragmentA, FragmentB, FragmentC. All fragments have one TextInputLayout with editText inside and button to go to next fragment. When FragmentA gets called I enter text in editText and go to next fragment (FragmentB) and do the same thing and do the same thing for last fragment (FragmentC). If I go back to FragmentB and type something different in editText and go to FragmentC text that was three is gone. I know that I have to pass bundle to fragment that will be displayed when swipe back on phone usingonDetach() method. I know if I want to pass bundle to fragment that I will call with pressing next button I have to setArguments(bundle).
public void switchFragments(Fragment fragment, Fragment currentFragment, boolean addToBackStack, String tag) {
FragmentTransaction transaction = currentFragment.getActivity().getSupportFragmentManager().beginTransaction();
transaction.add(R.id.fragment_container_sign_up, fragment, tag);
transaction.hide(currentFragment);
if(addToBackStack) {
transaction.addToBackStack(tag);
}
transaction.commit();
}
I am using transaction.add() and transaction.hide() because I am losing text entered in editTexts when I am coming back from previous fragment if I use transaction.replace()
To store and keep values between Fragments, look at SharedPreferences. Use this to pass value between fragments.
holder.YOURBUTTONCLICKID.setOnClickListener {
val sharedPrefs = context
.getSharedPreferences("PREFS", Context.MODE_PRIVATE)
sharedPrefs.edit().putString("postId", EDITTEXTID).apply()
(context as FragmentActivity)
.supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, FRAGMENTA/B/C )
.addToBackStack(null).commit()
}
Replace uppercase with relevant Ids.
And get the value on the other Fragment like this
val preference = context?.getSharedPreferences("PREFS", Context.MODE_PRIVATE)
if (preference != null) {
getTextValue = preference.getString("postId", "none").toString()
}
Remember that your putString key and getString has to be the same.
I have an activity that shows two fragments at the same time on tablet and one at a time on handset. Because I am making the app for both tablets and android, I have to separate the functionality of the navigation between fragments into a separate function "displaySecondFragmentOnHandset()".
On the smaller handset when a move from the first fragment to the second and the then try to go back to the first, the screen is blank.
MyActivity:
class CentralActivity : AppCompatActivity() {
val manager = supportFragmentManager
var firstFrag : FirstFrag? = null
override fun onCreate(savedInstanceState: Bundle?) {
...
var ft: FragmentTransaction = manager.beginTransaction();
firstFrag = FirstFrag.newInstance()
ft.add(R.id.real_container, firstFrag!!, "firstFrag")
if (screenLayoutSize >= Configuration.SCREENLAYOUT_SIZE_LARGE) {
var secondFrag = SecondFrag.newInstance()
ft.add(R.id.real_container, secondFrag, "secondFrag")
}
ft.commit()
}
fun displaySecondFragmentOnHandset (){
var secondFrag = SecondFrag.newInstance()
var ft: FragmentTransaction = manager.beginTransaction();
ft!!.replace(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
}
Then in my FirstFragemnt, if i'm on a smaller handset I do:
class FirstFragemnt : Fragment() {
private var viewModel: SharedViewModel? = null
fun goToSecondFragment(){
if (screenLayoutSize!! < Configuration.SCREENLAYOUT_SIZE_LARGE) {
viewModel!!.setMsgInCommunicator(collection)
var centralActivity: CentralActivity = activity as CentralActivity
centralActivity.displaySecondFragmentOnHandset()
}
}
The problem is when I press the back button to go to the first fragment, The screen is blank.
in CentralActivity replace
ft!!.replace(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
with
ft!!.add(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
The solution was to put the fun goToSecondFragment() inside of override fun onViewCreated
I've been stuck on this for a while now and will appreciate any guidance on this. Also not sure if my title is accurate. Inside my adapter I create a new fragment:
Adapter code:
cardAdoptDetailsFrag nextFrag = new cardAdoptDetailsFrag();
android.support.v4.app.Fragment callingFrag = ((FragmentActivity)context).getSupportFragmentManager().findFragmentByTag("TagFeedFragment");
FragmentTransaction ft = ((FragmentActivity)context).getSupportFragmentManager().beginTransaction();
ft.hide(callingFrag);
ft.add(R.id.fram, nextFrag,"cardAdoptDetailsFrag");
ft.addToBackStack("TagFeedFragment");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
Then inside my Main Activity where I manage all the fragments, I'm trying to check if the "cardAdoptDetailsFrag" isAdded. For some reason I cannot do it as per the below.
Get Fragment by tag. The below is where it fails with null object reference.
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
Log.d(TAG, "Check if added: "+frag.isAdded());
Now I know I can just add it inside a method and do a null check and return a boolean, but I know I'm doing something wrong here. Because with my other fragments the isAdded and remove works, but they get initiated inside the Main Activity where "cardAdoptDetailsFrag" gets initiated inside the adapter.
Example of Main Activity:
public class MainActivity extends AppCompatActivity implements adoptFeedFragment.OnFragmentInteractionListener,
lostAndFoundFragment.OnFragmentInteractionListener,
servicesFragment.OnFragmentInteractionListener,
userMenuFragment.OnFragmentInteractionListener,
showUserAdoptPostsFrag.OnFragmentInteractionListener{
adoptFeedFragment adoptFeedFragment;
lostAndFoundFragment lostAndFoundFragment;
servicesFragment servicesFragment;
userMenuFragment userMenuFragment;
....
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adoptFeedFragment = new adoptFeedFragment();
lostAndFoundFragment = new lostAndFoundFragment();
servicesFragment = new servicesFragment();
userMenuFragment = new userMenuFragment();
....
//Here I can do for example:
adoptFeedFragment.isAdded(); //Will simply return a boolean.
//Or I can do a remove:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.remove(adoptFeedFragment);
//But I cannot do the below:
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
Log.d(TAG, "frag.isAdded(): "+frag.isAdded());
*****Edited Post. Ok lets say the frag is added. Why can I not remove it using the below.
public boolean isAdoptDetailsFragAdded() {
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
if(frag == null){
return false;
} else {
return true;
}
}
public Fragment getAdoptDetailsFrag() {
return getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
}
//I'm unable to remove the fragment using the below:
Log.d(TAG, "showFeedFragment: "+isAdoptDetailsFragAdded()); <--returns true
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if(isAdoptDetailsFragAdded()) {
ft.remove(getAdoptDetailsFrag());
}
ft.commit();
//Now I check again wether it is still added. And still returns true even though I just removed it.
Log.d(TAG, "showFeedFragment: "+isAdoptDetailsFragAdded()); <-- Still returns true.
}
This is my fragment adding or replacing method which i am using in 13 projects.
You can use it and get rid of managing fragments.
/**
* replace or add fragment to the container
*
* #param fragment pass android.support.v4.app.Fragment
* #param bundle pass your extra bundle if any
* #param popBackStack if true it will clear back stack
* #param findInStack if true it will load old fragment if found
*/
public void replaceFragment(Fragment fragment, #Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
String tag = fragment.getClass().getName();
Fragment parentFragment;
if (findInStack && fm.findFragmentByTag(tag) != null) {
parentFragment = fm.findFragmentByTag(tag);
} else {
parentFragment = fragment;
}
// if user passes the #bundle in not null, then can be added to the fragment
if (bundle != null)
parentFragment.setArguments(bundle);
else parentFragment.setArguments(null);
// this is for the very first fragment not to be added into the back stack.
if (popBackStack) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
ft.addToBackStack(parentFragment.getClass().getName() + "");
}
ft.replace(R.id.main_frame_container, parentFragment, tag);
ft.commit();
fm.executePendingTransactions();
}
Here R.id.main_frame_container is FrameLayout in activity layout on which fragment is placed.
Here findFragmentByTag() is returning null when you do getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag").
findFragmentByTag() will return null in two cases.
If fragment cardAdoptDetailsFrag is not yet attached to view (you did not call add or replace for this fragment)
You added that fragment, but you are using different TAG, while searching that fragment.
Make sure if non of the case in your problem.
Now the solution is, you can modify your logic, like
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
if (frag != null && frag.isAdded()) {
// Do you task. Fragment is added
} else {
// Fragment is not added yet
}
I did not went through the long post but I read only this part.
Now I check again wether it is still added. And still returns true even though I just removed it.
And the reason behind that is FragmentTransaction.commit is not synchronous.
Read the docs for commit method:
*Schedules a commit of this transaction. The commit does
* not happen immediately; it will be scheduled as work on the main thread
* to be done the next time that thread is ready.*
If you need that to be synchronous, use commitNow instead.