I have two fragments and want to open second fragment (WordFragment) when pressing button of the first one(SearchFragment) by Bundle. But Fragment shwos default data instead of the passed one.
ClickListener in First Fragment:
searchDefAdapter = SearchDefAdapter(
object : SearchDefAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
val bundle = Bundle()
val arr = arrayOf("word", "слово", "I give you my word")
bundle.putStringArray(INFO_BUNDLE_ID, arr)
val wordFragment = WordFragment()
wordFragment.arguments = bundle
parentFragmentManager.beginTransaction().apply {
replace(R.id.searchFragment, WordFragment())
commit()
}
}
},
object : SearchDefAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
//viewModel.saveWord(position)
}
}
)
Second Fragment:
class WordFragment : Fragment() {
private var _binding: FragmentWordBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentWordBinding.inflate(inflater, container, false)
val view = binding.root
arguments?.let {
val translation = it.getStringArray(INFO_BUNDLE_ID)
if (translation != null){
binding.wordTitleTv.text = translation[0]
binding.translationTv.text = translation[1]
binding.exampleTv.text = translation[2]
}
}
return view
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}
this is because you create new WordFragment here replace(R.id.searchFragment, WordFragment()) instead of putting the one you added the bundle to it.
Related
I am repeating the process of adding and completing the list on the detail screen.
The problem is that the previous list remains intact in the newly created detail fragment.
For example, if i made 3 lists in the previous detail screen, adding data in the new detail screen will add them to a list of size 3.
I've tried various things, but I still don't know what could be the cause.
Fragment
class WriteDetailFragment : Fragment() {
...
private val viewModel: WriteDetailViewModel by viewModels {
WriteDetailViewModelFactory(
(requireActivity().application as WorkoutApplication).writeDetailRepo
)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentWriteDetailBinding.inflate(inflater, container, false)
binding.apply {
adapter = DetailAdapter()
rv.adapter = adapter
rv.itemAnimator = null
// add set
addSet.setOnClickListener {
viewModel.addSet()
}
// complete
complete.setOnClickListener {
findNavController().navigate(R.id.action_writeDetailFragment_to_addRoutineFragment)
}
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.items.collect { list ->
adapter.submitList(list)
}
}
}
}
}
ViewModel
class WriteDetailViewModel(
private val repository: WriteDetailRepository
): ViewModel() {
private var _items: MutableStateFlow<List<WorkoutSetInfo>> = MutableStateFlow(listOf())
val items = _items.asStateFlow()
fun addSet() {
viewModelScope.launch(Dispatchers.IO) {
repository.add()
_items.value = repository.getList()
}
}
}
Repository
class WriteDetailRepository(val dao: WorkoutDao) {
private var setInfoList = ArrayList<WorkoutSetInfo>()
private lateinit var updatedList: List<WorkoutSetInfo>
fun add() {
val item = WorkoutSetInfo(set = setInfoList.size + 1)
setInfoList.add(item)
updatedList = setInfoList.toList()
}
fun getList() : List<WorkoutSetInfo> = updatedList
}
I need to get session id to menu fragment. but it doesn't work. In this I get Session ID from Login Activity. Now I need to get that id to Menu fragment. In this one activity loaded five Fragments. I used bundle for load data.
Here's my activity.kt
class Home : AppCompatActivity() {
private val HomeFragment = HomeFragment()
private val CreditsFragment = CreditsFragment()
private val BusFragment = BusFragment()
private val NotificationsFragment = NotificationsFragment()
private val MenuFragment = MenuFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home)
val sessionId = intent.getStringExtra("EXTRA_SESSION_ID")
textView4.setText(sessionId)
replaceFragment(HomeFragment)
bottomNavBar.setOnNavigationItemSelectedListener{
when (it.itemId){
R.id.menu_home -> replaceFragment(HomeFragment)
R.id.menu_credits -> replaceFragment(CreditsFragment)
R.id.menu_bus -> replaceFragment(BusFragment)
R.id.menu_notification -> replaceFragment(NotificationsFragment)
R.id.menu_menu -> replaceFragment(MenuFragment)
}
true
}
val bundle = Bundle()
bundle.putString("EXTRA_SESSION_ID", sessionId)
val myObj = MenuFragment()
myObj.setArguments(bundle)
}
private fun replaceFragment (fragment:Fragment){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
here's my updated menu fragment.kt. I think I have problem in this. can u plzzz help me to find it.
class MenuFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
var sessionId = it.toString()
emailAddressText.setText(sessionId)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_menu, container, false)
}
companion object {
}
}
change activity to this :
val bundle = Bundle()
bundle.putString("EXTRA_SESSION_ID", sessionId)
MenuFragment.setArguments(bundle)
bottomNavBar.setOnNavigationItemSelectedListener{
when (it.itemId){
R.id.menu_home -> replaceFragment(HomeFragment)
R.id.menu_credits -> replaceFragment(CreditsFragment)
R.id.menu_bus -> replaceFragment(BusFragment)
R.id.menu_notification -> replaceFragment(NotificationsFragment)
R.id.menu_menu -> replaceFragment(MenuFragment)
}
true
}
in the fragment you also need to use arguments.getString("EXTRA_SESSION_ID") to retreive sessionId value. and place emailAddressText.setText(sessionId)
in onViewCreated method because in onCreate() method views are not ready yet.
so change the fragment like this :
class MenuFragment : Fragment() {
var sessionId = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
sessionId = it.getString("EXTRA_SESSION_ID","")
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_menu, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
emailAddressText.setText(sessionId)
}
}
The MenuFragment instance you have in your replaceFragment transaction is different from the one where you set the sessionid argument to.
Remove the other instantiation val myObj = MenuFragment() and set arguments on MenuFragment instead.
(Naming convention hint: it's easier to keep types and objects apart if types are CamelCase and types lowerCamelCase. For example, private val menuFragment = MenuFragment().)
The situation is pretty straightforward. I have a simple android app with 4 fragments displayed through a bottom navigation bar, and a central Room database. Each fragment should be able to perform CRUD operations on the DB through a viewmodel (details are probably irrelevant but I'll show this as well to be sure):
class ViewModel(application: Application): AndroidViewModel(application) {
val readAllIngredients: LiveData<List<Ingredient>>
val readAllRecipes: LiveData<List<Recipe>>
private val ingredientRepository: IngredientRepository
private val recipeRepository: RecipeRepository
init {
val ingredientDAO = ShoppingAppDatabase.getDatabase(application).ingredientDAO()
val recipeDAO = ShoppingAppDatabase.getDatabase(application).recipeDAO()
ingredientRepository = IngredientRepository(ingredientDAO)
recipeRepository = RecipeRepository(recipeDAO)
readAllIngredients = ingredientRepository.allIngredients
readAllRecipes = recipeRepository.allRecipes
}
fun addIngredient(ingredient: Ingredient) {
viewModelScope.launch(Dispatchers.IO) {
ingredientRepository.put(ingredient)
}
}
fun deleteIngredient(ingredient: Ingredient) {
viewModelScope.launch(Dispatchers.IO) {
ingredientRepository.delete(ingredient)
}
}
fun addRecipe(recipe: Recipe) {
viewModelScope.launch(Dispatchers.IO) {
recipeRepository.put(recipe)
}
}
fun updateRecipe(recipe: Recipe) {
viewModelScope.launch(Dispatchers.IO) {
recipeRepository.update(recipe)
}
}
fun updateIngredient(ingredient: Ingredient) {
viewModelScope.launch(Dispatchers.IO) {
ingredientRepository.update(ingredient)
}
}
}
I'm initializing a viewmodel in each fragment, but with limited success. Here's a fragment for which everything works fine:
class InventoryFragment() : Fragment() {
private var listAdapter = IngredientAdapter()
private lateinit var viewModel : ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_inventory, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listAdapter = IngredientAdapter()
recycler_view.apply {
layoutManager = LinearLayoutManager(activity)
adapter = listAdapter
}
viewModel = ViewModelProvider(this).get(ViewModel::class.java)
viewModel.readAllIngredients.observe(viewLifecycleOwner, Observer { ingredient -> listAdapter.setData(ingredient) })
add_button.setOnClickListener{
val errorMessages = validateInput()
if(errorMessages.isNotEmpty()) {
displayToast(activity, errorMessages)
}
else {
viewModel.addIngredient(Ingredient(
edit_name.text.toString(),
edit_qty.text.toString().toFloat(),
edit_um.text.toString()
))
listAdapter.notifyDataSetChanged()
displayToast(activity, "Ingredient added")
hideKeyboard(activity, requireView().windowToken)
}
clearInput()
}
}
}
I'm initializing it in the onViewCreated callback and yeah, it works fine. Doing the same thing in a different fragment yields.. different results for some reason.
class BrowseFragment() : Fragment() {
private lateinit var viewModel: ViewModel
var recipeAdapter = RecipeAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_browse, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(ViewModel::class.java)
viewModel.readAllRecipes.observe(viewLifecycleOwner, Observer { recipe -> recipeAdapter.setData(recipe) })
submit_button.setOnClickListener{
var submitFragment = SubmitFragment(recipeAdapter)
var tr = (view.context as FragmentActivity).supportFragmentManager.beginTransaction()
tr.replace(R.id.fragment_container, submitFragment)
tr.commit()
}
browse_recycler_view.apply {
layoutManager = LinearLayoutManager(activity)
adapter = recipeAdapter
}
}
}
When I try to initialize the viewmodel in onViewCreated, I get an IllegalStateException: Can't access ViewModels from detached fragment exception. Creating it in onCreate doesn't work either, since the lifecycle owner is null, which makes sense I guess. What exactly am I doing wrong here?
I have a fragment that I would like to use either as a full screen or as a Dialog content.
I'm trying to pass it this way:
companion object {
val DIALOG_TITLE = "DIALOG_TITLE"
fun getInstance(title: String, content: Fragment): BaseCustomDialogFragment {
return BaseCustomDialogFragment().apply {
arguments?.putString(DIALOG_TITLE, title)
activity?.fragmentAdd(content)
}
}
}
Obviously, this solution won't work. As long the Fragment hasn't been attached, we don't have access to the activity.
Is there any way to achieve the same outcome without triggering the load from the Activity?
I found a workaround for this. Instead of passing the Fragment, we include Fragment's class and arguments (Class<T> and Bundle).
Then, we create and add the fragment onViewCreated()
Here is the code:
class BaseCustomDialogFragment: Fragment() {
companion object {
val FRAGMENT_CLASS = "FRAGMENT_CLASS"
val ARGUMENTS_BUNDLE = "ARGUMENTS_BUNDLE"
fun <T> getInstance(title: String, fragmentClass: Class<T>, bundle: Bundle): BaseCustomDialogFragment {
return BaseCustomDialogFragment().apply {
arguments = Bundle()
arguments!!.putString(DIALOG_TITLE, title)
arguments!!.putSerializable(FRAGMENT_CLASS, fragmentClass)
arguments!!.putBundle(ARGUMENTS_BUNDLE, bundle)
}
}
}
private lateinit var className: Class<*>
private lateinit var bundle: Bundle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
(arguments?.getSerializable(FRAGMENT_CLASS) as? Class<*>)?.let { className = it }
arguments?.getBundle(ARGUMENTS_BUNDLE)?.let { bundle = it }
// Inflate View.
return inflater.inflate(R.layout.fragment_custom_dialog, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setContentUp()
}
private fun setContentUp(){
val fragment = createFragment(className, bundle)
activity?.supportFragmentManager
?.beginTransaction()
?.add(R.id.fragment_dialog_placeholder, fragment)
?.disallowAddToBackStack()
?.commit()
}
private fun <T> createFragment(fragmentClass: Class<T>, arguments: Bundle): Fragment {
val fragment = fragmentClass.newInstance() as Fragment
fragment.arguments = arguments
return fragment
}
I hope it helps to somebody.
class UserFragment : Fragment(), View.OnClickListener {
private val userBinding: FragmentUserBinding by onCreateView<Fragment, FragmentUserBinding>(R.layout.fragment_user)
val user: ObservableField<String> = ObservableField()
private var bundle = Bundle()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userBinding.main = this
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = userBinding.root!!
override fun onClick(p0: View?) {
runAnimation(500L, Techniques.RubberBand, p0)
Handler().postDelayed({
saveUserAndStartLetterFragment()
}, 700L)
}
private fun saveUserAndStartLetterFragment() {
var fragment = WordpackChooserFragment()
bundle.putString("User",user.get())
fragment.arguments = bundle
activity!!.supportFragmentManager.replaceFragment(fragment, activity!!.findViewById(R.id.flContainer))
}
}
In this fragment I declare the bundle. I do not specify it as nullable.
class WordpackChooserFragment : Fragment(), View.OnClickListener {
private val wordpackChooserBinding: FragmentWordpackChooserBinding by onCreateView<Fragment, FragmentWordpackChooserBinding>(R.layout.fragment_wordpack_chooser)
private var bundle: Bundle = Bundle()
private lateinit var fragment: Fragment
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
wordpackChooserBinding.main = this
setRecycler()
bundle = arguments
return wordpackChooserBinding.root
}
override fun onClick(v: View?) {
fragment = RoundsChooserFragment()
bundle.putStringArrayList("Wordpack", selectedPack)
fragment.arguments = bundle
activity!!.supportFragmentManager.replaceFragment(fragment, activity!!.findViewById(R.id.flContainer))
}
private fun setRecycler() {
wordpackChooserBinding.root.btn_recycler.layoutManager = LinearLayoutManager(this.context)
wordpackChooserBinding.root.btn_recycler.adapter = BtnAdapter(this)
wordpackChooserBinding.root.btn_recycler.adapter.notifyDataSetChanged()
}
}
In this fragment this line bundle = arguments errors with the following:
Type mismatch.
Required: Bundle
Found: Bundle?
I can use the non-null assertion operator '!!' but It seems like a hack.
arguments is nullable (note the "if any" in the description of the link), therefore you can not simply assign it to a non-nullable Bundle. You would either handle the case of it being null (using an if), using !! or you could write something like this:
arguments?.let { bundle = it }
However, I'd say its preferred to use let.