Saving some data in Kotlin fragments - android

I have 2 fragments, first(start_fragment) should shows when app starts and have 2 buttons, 1 which close app(declineButton), and second which hide this fragment and show main fragment(webview_fragment). If user press agreeButton, next time app will starts already in main fragment(webview_fragment). How could i save that user press agree button? I tried to create boolean variable but can't imagine how can i put new value from button within fragment to variable within mainactivity.
MainActivity.kt
package com.example.webviewapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
var firstlauch: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (firstlauch == true) {
supportFragmentManager
.beginTransaction()
.replace(R.id.fullcreen_holder, start_fragment())
.commit()
} else {
supportFragmentManager
.beginTransaction()
.replace(R.id.webview_holder, webview_fragment())
}
}
}
start_fragment.kt
package com.example.webviewapp
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
class start_fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.layout_start_fragment, container, false)
val agreeButton: Button = view.findViewById(R.id.privacy_agree)
val declineButton: Button = view.findViewById(R.id.privacy_decline)
agreeButton.setOnClickListener {
val fragment = webview_fragment()
val transaction = fragmentManager?.beginTransaction()
transaction?.remove(this)?.replace(R.id.webview_holder, fragment)?.commit()
}
declineButton.setOnClickListener {
requireActivity().finishAndRemoveTask()
}
return view
}
}

You can use sharedPreferences for keep small data.
In MainActivity
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
val firstLaunch = sharedPref.getBoolean("YOUR_KEY", true)
In Fragment
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
val editor = sharedPref.edit()
agreeButton.setOnClickListener {
editor.putBoolean("YOUR_KEY", true)
val fragment = webview_fragment()
val transaction = fragmentManager?.beginTransaction()
transaction?.remove(this)?.replace(R.id.webview_holder, fragment)?.commit()
}
declineButton.setOnClickListener {
editor.putBoolean("YOUR_KEY", false)
requireActivity().finishAndRemoveTask()
}

Related

Trying to pass a string from an activity to a fragment when login is successful (Kotlin)

I'm developing an app where I made a login and register with firebase. I'm trying to pass a string from an activity to a fragment and The function that sends the strings gets called when the login or the register is successful. Then the string passed to the fragment is shown in a textView, but that doesn't happen. This is the Register class when the function is created. Thanks in advance!
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import com.google.firebase.auth.FirebaseAuth
import kotlinx.android.synthetic.main.activity_register.*
class Register : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register)
setup()
}
private fun setup(){
registerBtn.setOnClickListener{
if(emailEditText.text.isNotEmpty() && passwordEditText.text.isNotEmpty()){
FirebaseAuth.getInstance().createUserWithEmailAndPassword(emailEditText.text.toString(),
passwordEditText.text.toString()).addOnCompleteListener{
if(it.isSuccessful){
//If email doesn't exists send empty string(shouldn't happen,
//already controlled with createUserWithEmailAndPassword.isSuccesful
passUserData()
showMainMenu()
}else{
showAlert()
}
}
}
}
loginBtn.setOnClickListener{
if(emailEditText.text.isNotEmpty() && passwordEditText.text.isNotEmpty()){
FirebaseAuth.getInstance().signInWithEmailAndPassword(emailEditText.text.toString(),
passwordEditText.text.toString()).addOnCompleteListener{
if(it.isSuccessful){
//If email doesn't exists send empty string(shouldn't happen,
//already controlled with createUserWithEmailAndPassword.isSuccesful
passUserData()
showMainMenu()
}else{
showAlert()
}
}
}
}
}
private fun showAlert(){
val builder = AlertDialog.Builder(this)
builder.setTitle("Error")
builder.setMessage("Se ha producido un error al intentar autenticar el usuario")
builder.setPositiveButton("Acceptar", null)
val dialog: AlertDialog = builder.create()
dialog.show()
}
private fun showMainMenu(){
val goMainMenu = Intent(this, MainActivity::class.java)
startActivity(goMainMenu)
}
private fun passUserData(){
val bundle = Bundle()
bundle.putString("sayhi", "Hello")
val transact = supportFragmentManager.beginTransaction()
val myFrag = FourthFragment()
myFrag.arguments = bundle
transact.commit()
}
}
And this is the fragment (as you can see I tried to put the same code in both methods to see if it worked):
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
enum class ProviderType{
BASIC
}
class FourthFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
if (arguments != null){
val sayHello = requireArguments().getString("sayhi")
val showtext : TextView = requireView().findViewById(R.id.emailShow)
showtext.text = "$sayHello"
}
return inflater.inflate(R.layout.fragment_fourth, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (arguments != null){
val sayHello = requireArguments().getString("sayhi")
val showtext : TextView = requireView().findViewById(R.id.emailShow)
showtext.text = "$sayHello"
}
}
}

Can I save the state of my viewModel that holds data for my fragments?

I am new to Android app dev,I want my fragment to store user information in a viewModel, yet that model is accessed by other fragments. The app runs well but I would like it to store the viewModel even if the app is destroyed. How can I save the state of my model?
This is my viewModel class (it starts with the package name):
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
/**
* This model is shared by related views.
* stores user name1 and name2 that prevents repetition in filling forms.
* stores data lost during configuration changes.
* stores data from the form.
* */
class SharedViewModel() : ViewModel() {
private val _nameOne = MutableLiveData<String>()
val nameOne: LiveData<String> = _nameOne
private val _nameTwo = MutableLiveData<String>()
val nameTwo: LiveData<String> = _nameTwo
private val _emailAddress = MutableLiveData<String>()
val emailAddress: LiveData<String> = _emailAddress
private val _phoneNumber = MutableLiveData<String>()
val phoneNumber: LiveData<String> = _phoneNumber
private val _nationalIdNumber = MutableLiveData<String>()
val nationalIdNumber: LiveData<String> = _nationalIdNumber
fun saveUserData(
nameA: String, nameB: String,
emailA: String, phoneN: String,
natId: String) {
_nameOne.value = nameA
_nameTwo.value = nameB
_emailAddress.value = emailA
_phoneNumber.value = phoneN
_nationalIdNumber.value = natId
}
}
This is the MainActivity class (it starts with the package name):
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupActionBarWithNavController
import ke.co.qaizen.qaizencarrental.databinding.ActivityMainBinding
/**
* Main Activity and entry point for the app.
*/
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread.sleep(3)
installSplashScreen()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Get the navigation host fragment from this Activity
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
// Instantiate the navController using the NavHostFragment
navController = navHostFragment.navController
// Make sure actions in the ActionBar get propagated to the NavController
setupActionBarWithNavController(navController)
}
/**
* Enables back button support. Simply navigates one element up on the stack.
*/
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
And this is my fragment class (it also starts with the package name):
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import ke.co.qaizen.qaizencarrental.databinding.FragmentAccountBinding
import ke.co.qaizen.qaizencarrental.mainmodel.SharedViewModel
class AccountFragment : Fragment() {
private var _binding: FragmentAccountBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAccountBinding.inflate(inflater, container, false)
sharedViewModel.nameOne.observe(viewLifecycleOwner) { nameOne ->
binding.userNameOne.text = nameOne
}
sharedViewModel.nameTwo.observe(viewLifecycleOwner) { nameTwo ->
binding.userNameTwo.text = nameTwo
}
sharedViewModel.emailAddress.observe(viewLifecycleOwner) { emailAddress ->
binding.userEmailAddress.text = emailAddress
}
sharedViewModel.phoneNumber.observe(viewLifecycleOwner) { phoneNumber ->
binding.userPhoneNumber.text = phoneNumber
}
sharedViewModel.nationalIdNumber.observe(viewLifecycleOwner) {
nationalIdNumber ->
binding.userIdNumber.text = nationalIdNumber
}
return binding.root
}
}
Try the Google Room DB library.
Easy store objects.

Android Kotlin - Navigate between fragments using textView in recyclerView

I would like to navigate between fragments using a textView inside a recyclerview.
Currently I am successfully navigating between fragments using the item, but I would like to go further and navigate using the individual textViews within an item.
The recyclerview is inflated from a local room database.
Below is my adapter
package com.benb.inventory
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.benb.inventory.data.Item
import com.benb.inventory.data.getFormattedPrice
import com.benb.inventory.databinding.ItemListItemBinding
class ItemListAdapter(private val onItemClicked: (Item) -> Unit) :
ListAdapter<Item, ItemListAdapter.ItemViewHolder>(DiffCallback) {
class ItemViewHolder(internal var binding: ItemListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
binding.apply {
itemName.text = item.itemName
itemPrice.text = item.getFormattedPrice()
itemShop.text = item.shop.toString()
itemShop.setOnClickListener{
val shopName = itemShop.text.toString()
Toast.makeText(root.context, "Clicked: ${item.shop}", Toast.LENGTH_SHORT ).show()
}
}
}
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.itemName == newItem.itemName
}
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ItemListAdapter.ItemViewHolder {
return ItemViewHolder(ItemListItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: ItemListAdapter.ItemViewHolder, position: Int) {
val current = getItem(position)
holder.itemView.setOnClickListener {
onItemClicked(current)
}
holder.binding.itemShop.setOnClickListener {
onItemClicked(current)
val shopName = current.shop
fun showShopList(shopName: String) {
val shopName = holder.binding.itemShop.text.toString()
val action = ItemListFragmentDirections.actionItemListFragmentToShopItemFragment(shopName)
}
}
holder.bind(current)
}
}
This is the fragment that contains the list
package com.benb.inventory
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.benb.inventory.databinding.ItemListFragmentBinding
import kotlinx.coroutines.InternalCoroutinesApi
#InternalCoroutinesApi
class ItemListFragment : Fragment() {
#InternalCoroutinesApi
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database.itemDao()
)
}
private var _binding: ItemListFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ItemListFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = ItemListAdapter {
val action = ItemListFragmentDirections.actionItemListFragmentToItemDetailFragment(it.id)
this.findNavController().navigate(action)
}
binding.recyclerView.adapter = adapter
viewModel.allItems.observe(this.viewLifecycleOwner) {items ->
items.let {
adapter.submitList(it)
}
}
binding.itemShop.setOnClickListener{
val shop = binding.itemShop.text.toString()
val action = ItemListFragmentDirections.actionItemListFragmentToShopItemFragment(shop)
viewModel.retrieveShopItems(shop)
this.findNavController().navigate(action)
}
binding.recyclerView.layoutManager = LinearLayoutManager(this.context)
binding.floatingActionButton.setOnClickListener {
val action = ItemListFragmentDirections.actionItemListFragmentToAddItemFragment(
getString(R.string.add_fragment_title)
)
this.findNavController().navigate(action)
}
}
}
For clarity this is what the inflated recyclerview looks like, it can contain many items, I have only added one.
At the moment if I click anywhere on the item it takes you to a screen with more detail about the fragment.
[A screenshot of the recycler view][1]
I would like to be able to navigate to a specific fragment depending on the textView clicked.
For example one fragment that only contains other products from the same shop.
As you may notice, I have been able to add an onClickListener in the ViewHolder class, it creates a Toast.
I have not had success in using the same onClickListener to navigate.
Thank you very much.
P.S. Any other advice is welcome, particularly if anyone knows what the #internalcoroutinesAPI thing is about please tell me!
[1]: https://i.stack.imgur.com/k6Td3.png

Kotlin: update fragment in viewPager

I am considering a simple example consists of the following component:
MainActivity: has editText, button, viewPager
FragmentA: has nothing
FragmentB: has a textView
So, in the screen, you can swipe to see each fragment. I want my example to have the function: if one writes a text in the editText and click the button then it shows up in Fragment B.
A tried it by using notifyDatsetChanged() but it does not work. Could anyone help? Below is the whole code of mine:
MainActivity.kt
package com.myproject.chapterfivesectionone
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.myproject.chapterfivesectionone.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
lateinit var fragmentB: FragmentB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val fragmentList = listOf(FragmentA(), FragmentB())
val adapter = FragmentAdapter(this)
adapter.fragmentList = fragmentList
binding.viewPager.adapter = adapter
binding.buttonSend.setOnClickListener {
var bundle = Bundle()
bundle.putString("key1", binding.editTextWriteSomething.text.toString())
fragmentB.arguments = bundle
adapter.notifyDataSetChanged()
}
}
}
FragmentAdapter.kt
package com.myproject.chapterfivesectionone
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class FragmentAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
var fragmentList = listOf<Fragment>()
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList.get(position)
}
}
FragmentA.kt
package com.myproject.chapterfivesectionone
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class FragmentA : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_a, container, false)
}
}
FragmentB.kt
package com.myproject.chapterfivesectionone
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.myproject.chapterfivesectionone.databinding.FragmentBBinding
class FragmentB : Fragment() {
lateinit var binding: FragmentBBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentBBinding.inflate(inflater, container, false)
if (arguments?.getString("key1") != null) {
binding.textViewInFragmentB.text = arguments?.getString("key1")
}
return binding.root
}
}
Currently, fragmentB is inside fragmentList so you can retrieve it like fragmentList[fragmentBPosition]. Then defind a function inside FragmentB for update the TextView
binding.buttonSend.setOnClickListener {
(fragmentList[1] as FragmentB).updateTheTextView("new")
// if you want code more clean, can replace hard code value by a constant or you can write some logic to find FragmentB from fragmentList
}
FragmentB
class FragmentB : Fragment() {
fun updateTheTextView(newText : String) {
binding.textViewInFragmentB.text = newText
}
}

Fragment$Companion cannot be cast to androidx.fragment.app.Fragment on BottomNavigationView listener

My bottom navigation view should work with tabs, but in some way I cannot cast
fragment as Fragment
anymore with AndroidX
There is crash every time when I click on menu item
java.lang.ClassCastException: com.spacexmonitor.MissionListFragment$Companion cannot be cast to androidx.fragment.app.Fragment
at com.spacexmonitor.MainActivity$onCreate$menuOnNavigationItemSelectedListener$1.onNavigationItemSelected(MainActivity.kt:28)
There is my Activity:
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
if (savedInstanceState == null) {
fragmentTransaction.replace(R.id.container, MissionListFragment())
fragmentTransaction.commit()
}
val menuOnNavigationItemSelectedListener =
bottomNavigationBar.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.launchesMenuItem -> {
val fragment = MissionListFragment
fragmentTransaction.replace(R.id.container, fragment as Fragment)
.commit()
return#setOnNavigationItemSelectedListener true
}
R.id.chartsMenuItem -> {
val fragment = MissionChartFragment
fragmentTransaction.replace(R.id.container, fragment as Fragment)
.commit()
return#setOnNavigationItemSelectedListener true
}
}
false
}
}
}
My empty fragment:
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class MissionChartFragment : Fragment() {
companion object {
private val MISSION_CHART = "mission chart"
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_mission_chart, container, false)
}
}
A lot of answers here is to change app.Fragment to v4.Fragment usage, but this solution is not helping with my issue.
Change
val fragment = MissionListFragment
to
val fragment = MissionListFragment()
Using fragment = MissionListFragment you are linking the companion object of the MissionChartFragment

Categories

Resources