I created a Login/Register Page and I get this error while trying to click Register Button.This method should send me to Register Page where I can register a new account.The problem is I get this error when I click the register button: android app code error
How can I resolve this error ?
Here is my code and other screenshots:
class LoginFragment : Fragment() {
private lateinit var username: EditText
private lateinit var password: EditText
private lateinit var fAuth: FirebaseAuth
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_login, container, false)
username = view.findViewById(R.id.log_username)
password = view.findViewById(R.id.log_password)
fAuth = Firebase.auth
view.findViewById<Button>(R.id.btn_register).setOnClickListener {
val navRegister = activity as FragmentNav
navRegister.navigateFrag(RegisterFragment(),false)
}
view.findViewById<Button>(R.id.btn_login).setOnClickListener {
validateForm()
}
return view
}
private fun firebaseSignIn(){
fAuth.signInWithEmailAndPassword(username.text.toString(),password.text.toString()).addOnCompleteListener{
task ->
if(task.isSuccessful){
}else{
Toast.makeText(context,"Register Successful",Toast.LENGTH_SHORT).show()
var navHome = activity as FragmentNav
navHome.navigateFrag(HomeFragment(),addToStack = true)
}
}
}
private fun validateForm() {
val icon = AppCompatResources.getDrawable(requireContext(), R.drawable.erroricon)
icon?.setBounds(0, 0, icon.intrinsicWidth, icon.intrinsicHeight)
when {
TextUtils.isEmpty(username.text.toString().trim()) -> {
username.setError("Please Enter Username")
}
TextUtils.isEmpty(password.text.toString().trim()) -> {
password.setError("Please Enter Password")
}
username.text.toString().isNotEmpty() &&
password.text.toString().isNotEmpty() -> {
if (username.text.toString().matches(Regex("[a-zA-Z0-9._]+#[a-z]+\\\\.+[a-z]+"))) {
firebaseSignIn()
} else {
username.setError("Please Enter Valid Email Id")
}
}
}
}
I get this error here:
error
FragmentNav {
fun navigateFrag(fragment: Fragment, addToStack:Boolean )
}
Here is the navigation between buttons register/login :
class LoginActivity : AppCompatActivity(R.layout.login_activity),FragmentNav {
private lateinit var fAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.login_activity)
fAuth = Firebase.auth
val currentUser = fAuth.currentUser
if(currentUser !=null){
supportFragmentManager.beginTransaction()
.add(com.google.android.material.R.id.container,HomeFragment()).addToBackStack(null)
.commit()
}
supportFragmentManager.beginTransaction()
.add(com.google.android.material.R.id.container,LoginFragment())
.commit()
}
override fun navigateFrag(fragment: Fragment, addToStack: Boolean) {
val transaction = supportFragmentManager
.beginTransaction()
.replace(com.google.android.material.R.id.container,fragment)
if(addToStack){
transaction.addToBackStack(null)
}
transaction.commit()
}
}
That interface is totally useless if you are not passing the MainActivity context to the LoginFragment. Change LoginFragment constructor to this:
class LoginFragment(private val mListener: FragmentNav) : Fragment()
and in onClick() of the register button, call navigateFrag like this:
mListener.navigateFrag(RegisterFragment(),false)
And finally in MainActivity, pass context to LoginFragment like this:
supportFragmentManager.beginTransaction()
.add(com.google.android.material.R.id.container,LoginFragment(this))
.commit()
Related
I'm trying to inflate a custom dialog in my "CreateShoppingListMenuFragment" I've followed android's documentation but seem to be having a problem with the Listener, I know FragmentManager() is deprecated and used both parentFragmentManager & child FragmentManager to no success, maybe it's related?
Here is the error message:
logo1200.shoppinglist, PID: 24194
java.lang.ClassCastException: com.camilogo1200.shoppinglist.presentation.MainActivity#11852bbmust implement ShoppingListNameRequestListener
at com.camilogo1200.shoppinglist.presentation.fragments.ShoppingListNameRequestDialog.onAttach(ShoppinListNameRequestDialog.kt:68)
at androidx.fragment.app.Fragment.performAttach(Fragment.java:2922)
at androidx.fragment.app.FragmentStateManager.attach(FragmentStateManager.java:464)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:275)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
This is my DialogFragment:
class ShoppingListNameRequestDialog : DialogFragment() {
private lateinit var listener: ShoppingListNameRequestListener
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
val inflater: LayoutInflater = requireActivity().layoutInflater
val requestNameView = inflater.inflate(R.layout.shopping_list_name_request_dialog, null)
val nameInput = requestNameView.findViewById<TextView>(R.id.shopping_list_dialog_input)
var listName = ""
builder.setView(requestNameView)
.setPositiveButton(R.string.save_shopping_list,
DialogInterface.OnClickListener {dialog, id ->
if(nameInput.text.toString() != "")
listName = nameInput.text.toString()
listener.onDialogPositiveClick(this,listName);
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener{dialog, id ->
listener.onDialogNegativeClick(this)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
interface ShoppingListNameRequestListener {
fun onDialogPositiveClick(dialog: DialogFragment,listName:String)
fun onDialogNegativeClick(dialog: DialogFragment)
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
listener = context as ShoppingListNameRequestListener
} catch (e: ClassCastException) {
throw ClassCastException((context.toString() +
"must implement ShoppingListNameRequestListener"))
}
}
This is my "CreateShoppingListMenuFragment" (the host fragment where I'm inflating the dialog):
class CreateShoppingListMenuFragment : Fragment(),
ShoppingListNameRequestDialog.ShoppingListNameRequestListener {
private lateinit var binding: FragmentCreateShoppingListMenuBinding
private val viewModel: CreateShoppingListMenuViewModel by activityViewModels()
private val args: CreateShoppingListMenuFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(
layoutInflater,
R.layout.fragment_create_shopping_list_menu,
container,
false
)
binding.lifecycleOwner = this
viewModel.createItems()
viewModel.viewState.observe(viewLifecycleOwner, ::handleViewState)
val login = args.ownerName
val listId = args.listId
viewModel.setOwnerAndList(login, listId)
binding.createItemButton.setOnClickListener {
val directionToFragment =
CreateShoppingListMenuFragmentDirections.actionCreateShoppingListMenuFragmentToCreateItemMenuFragment(
login,
listId
)
Navigation.findNavController(binding.root).navigate(directionToFragment)
}
binding.completeShoppingListButton.setOnClickListener {
showNoticeDialog()
}
return binding.root
}
private fun showNoticeDialog() {
val dialog = ShoppingListNameRequestDialog()
dialog.show(parentFragmentManager, "ShoppingListNameRequestDialog")
}
override fun onDialogPositiveClick(dialog: DialogFragment,listName: String) {
val result = viewModel.saveShoppingList(listName)
Log.i("shoppingListResult", "$result")
// travel to final fragment sent shoppinglist as arg
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
// User touched the dialog's negative button
}
private fun handleViewState(viewState: CreateShoppingListMenuViewState) {
when (viewState) {
is CreateShoppingListMenuViewState.ErrorViewState -> showError(viewState.exception as ShoppingException)
//is RegisterViewState.SuccessViewState ->showSuccess()
else -> showSuccess(viewState)
}
}
private fun showSuccess(viewState: CreateShoppingListMenuViewState) {
val receivedList = viewState as CreateShoppingListMenuViewState.SuccessViewState
val dataList = receivedList.data
val adapter = ShoppingListMenuAdapter(dataList, viewModel::changeItemCount)
binding.itemListArray.adapter = adapter
}
private fun showError(exception: ShoppingException) {
if (exception.idError as? ItemError == ItemError.NO_ITEMS_CREATED) {
val message = getString(R.string.no_items_created_error_messages)
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
}
Any help would be greatly appreciated!
The Context in onAttach(Context context) is context Activity fragment does not have its own Context .
The problem here is you are casting context to ShoppingListNameRequestListener for this to work your Activity needs to implement the listener .
To solve this problem there are several ways. if we go with your approach we can pass fragment instance as targetFragment and use it as listener inside the DialogFragment .
private fun showNoticeDialog() {
val dialog = ShoppingListNameRequestDialog()
dialog.setTargetFragment(this)
dialog.show(parentFragmentManager, "ShoppingListNameRequestDialog")
}
Then inside dialog you can do something like this .
class ShoppingListNameRequestDialog:DialogFragment(){
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
targetFragment?.let {
listener = it as ShoppingListNameRequestListener
}
}
}
However setTargetFragment is deprecated now. As a Alternate way you can do the same with a shared ViewModel or with the new API FragmentResultListener.
Fist of all, I am using bottomNavigation which has five fragments on the MainAcitvity .
The problem is down below.
I am using RegisterForActivityResult for gallery image in the fragmentA.
when I use that before changing to other fragments, It's perfectly fine.
However, after changing to different Fragments
and back to the fragmentA then call RegisterForActivityResult again, the callback is not triggered.
when my MainActivity is with in Condition1 I had a problem.
When my MainActivity is with in Condition2 RegisterForActivityResult callback was fine.
condition1----------------------------------------------------------------------
I Initialized fragment variables in the onCreate on MainActivity , and BottomNavigationListener as well.
Condition2----------------------------------------------------------------------
I Initialized fragment variables in the onStart on MainActivity , and BottomNavigationListener as well.
Does anybody know why?
** Problem Code ↓ condition1**
class Main : AppCompatActivity() ,BottomNavigationView.OnNavigationItemSelectedListener {
//viewbinding
private lateinit var binding: Activity4MainBinding
//fragment Verables
private lateinit var homeFragment :Fragment_Home
private lateinit var liveFragment :Fragment_Live
private lateinit var boardFragment :Fragment_Board
private lateinit var chatFragment :Fragment_Chat
private lateinit var mypageFragment :Fragment_Mypage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity4MainBinding.inflate(layoutInflater)
setContentView(binding.root)
homeFragment = Fragment_Home.newInstance()
liveFragment = Fragment_Live.newInstance()
boardFragment = Fragment_Board.newInstance()
chatFragment = Fragment_Chat.newInstance()
mypageFragment = Fragment_Mypage.newInstance()
binding.bottomNavigationView.setOnNavigationItemSelectedListener(this)
//first fragment when the App started
supportFragmentManager.beginTransaction().add(R.id.main_layout,
homeFragment).commitAllowingStateLoss()
}
override fun onStart() {
super.onStart()
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.home -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, homeFragment).commit()
}
R.id.live -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, liveFragment).commit()
}
R.id.board -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, boardFragment).commit()
}
R.id.chat -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, chatFragment).commit()
}
R.id.mypage -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, mypageFragment).commit()
}
}
return true
}
}
** Working Code ↓ condition2**
class Main : AppCompatActivity() ,BottomNavigationView.OnNavigationItemSelectedListener {
//viewbinding
private lateinit var binding: Activity4MainBinding
//fragment Verables
private lateinit var homeFragment :Fragment_Home
private lateinit var liveFragment :Fragment_Live
private lateinit var boardFragment :Fragment_Board
private lateinit var chatFragment :Fragment_Chat
private lateinit var mypageFragment :Fragment_Mypage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity4MainBinding.inflate(layoutInflater)
setContentView(binding.root)
homeFragment = Fragment_Home.newInstance()
//first fragment when the App started
supportFragmentManager.beginTransaction().add(R.id.main_layout,
homeFragment).commitAllowingStateLoss()
}
override fun onStart() {
super.onStart()
liveFragment = Fragment_Live.newInstance()
boardFragment = Fragment_Board.newInstance()
chatFragment = Fragment_Chat.newInstance()
mypageFragment = Fragment_Mypage.newInstance()
binding.bottomNavigationView.setOnNavigationItemSelectedListener(this)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.home -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, homeFragment).commit()
}
R.id.live -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, liveFragment).commit()
}
R.id.board -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, boardFragment).commit()
}
R.id.chat -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, chatFragment).commit()
}
R.id.mypage -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, mypageFragment).commit()
}
}
return true
}
}
FragmentA code↓
class FragmentA : Fragment() {
// binding
lateinit var binding:FragmentABinding
//for instance from outside of the fragment
fun newInstance() : FragmentA{
return FragmentA()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = FragmentABinding.inflate(inflater, container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState)
binding.button.setOnClickListener{
requestImagesFromGallery.launch("image/*")}
}
private val
RequestIamgeFromGallery=registerForActivityResult(ActivityResultContracts.GetContent()) {
//If callback is triggered, returned uri set into the Imageview
binding.mypageImage.setImageURI(uri)
}
}
Please give me some comments why those Fragments instance in the MainActivity onStart work, but onCreate is not working. Also is it ok if I use the condition2?
I'm using Navigation Component, although I don't think that's the problem. The thing is that when I'm in a fragment that contains a ViewPager and I navigate to another one, when I go back using the back button or the gesture of the mobile phone, it returns to the previous fragment but it stops showing the ViewPager. I'll leave you my code for that fragment:
class HomeFragment : Fragment() {
private lateinit var homeFragmentViewModel: HomeFragmentViewModel
private var listAdapter: FlagsListAdapter? = null
private var regionName: String? = null
private val hashtagLabel: TextView by lazy { home_fragment__label__hashtag }
private val flagViewPager: ViewPager by lazy { home_fragment__viewpager__countries }
private val countryLabel: TextView by lazy { home_fragment__label__country_name }
private val showCasesButton: Button by lazy { home_fragment__button__country_cases }
companion object {
fun newInstance(): HomeFragment {
return HomeFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeFragmentViewModel = ViewModelProvider(this).get(HomeFragmentViewModel::class.java)
homeFragmentViewModel.getCountriesFlagLiveData().observeOnce(viewLifecycleOwner, Observer {
setFlagsAdapter(it)
})
showCasesButton.setOnClickListener {
val actionNavigateToShowCasesFragment = HomeFragmentDirections.navigateHomeFragmentToShowCasesFragment()
regionName?.let { regionName -> actionNavigateToShowCasesFragment.regionName = regionName }
it.findNavController().navigate(actionNavigateToShowCasesFragment)
}
setFormatHashtag()
}
private fun setFlagsAdapter(flagModelList: List<FlagModel>) {
listAdapter = context?.let {
FlagsListAdapter(
flagModelList,
it
)
}
flagViewPager.adapter = listAdapter
flagViewPager.setPadding(130, 0, 130, 0)
flagViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
Toast.makeText(GlobalApplication.getContextFromApplication, "Hola", Toast.LENGTH_SHORT).show()
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
countryLabel.text = ""
countryLabel.text = flagModelList[position].regionName
regionName = flagModelList[position].regionName
}
override fun onPageSelected(position: Int) {
countryLabel.text = flagModelList[position].regionName }
})
}
private fun setFormatHashtag() {
val text = getString(R.string.home_fragment_hashtag)
val spannableString = SpannableString(text)
val foregroundColorSpan = context?.let {
ForegroundColorSpan(ContextCompat.getColor(it, R.color.hashtagColor))
}
spannableString.setSpan(foregroundColorSpan, 0, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
hashtagLabel.text = spannableString
}
}
This is my activity:
class MainActivity : AppCompatActivity() {
private val navigationBottomBar by lazy { activity_main__navigation_view__bottom_bar }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setUpNavigation()
}
private fun setUpNavigation() {
val navController = Navigation.findNavController(this, R.id.activity_main__graph__nav_host)
NavigationUI.setupWithNavController(navigationBottomBar, navController)
}
}
When you load the fragment the first time it is shown like this, which is how it should be shown, and if I use the Bottom Navigation View it does well too:
But when I use the back button on my phone, here's what happens:
The problem is in your HomeFragment,setFlagsAdapter(it)` doesn't get called when you come back to this fragment. Either
change observeOnce to observe
or
Move
homeFragmentViewModel.getCountriesFlagLiveData().observeOnce(viewLifecycleOwner, Observer {
setFlagsAdapter(it)
})
from onCreateView to
override fun onResume(){
super.onResume()
homeFragmentViewModel.getCountriesFlagLiveData().observeOnce(viewLifecycleOwner, Observer {
setFlagsAdapter(it)
})
}
I'm trying to understand the concepts of MVVM but i'm having a hard time trying to understand how to communicate between The model class and UI (The fragment) in this case.
Here's the (shitty, be aware) code:
LoginFragment.kt
class LoginFragment: Fragment(), AuthListener {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<CredentialsLoginFragmentBinding>(
inflater,
R.layout.credentials_login_fragment,
container,
false
)
val viewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
val view: View = binding.root
val registerButton: Button = view.findViewById(R.id.register_button)
binding.viewModel = viewModel
viewModel.authListener = this
registerButton.setOnClickListener {
val transaction: FragmentTransaction? = fragmentManager?.beginTransaction()
transaction?.replace(R.id.fragment_container, SignupFragment())?.commit()
}
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val constraintRoot: MotionLayout = view.findViewById(R.id.sign_in_root)
ActivityUtils().switchLayoutAnimationKeyboard(constraintRoot = constraintRoot)
}
override fun onStarted() {
Toast.makeText(context, "Started", Toast.LENGTH_SHORT).show()
}
override fun onSuccess() {
Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show()
}
override fun onError(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}}
LoginViewModel.kt
class LoginViewModel: ViewModel(){
var username: String? = null
var password: String? = null
var isCredentialsValid: Boolean = false
var authListener: AuthListener? = null
private val context: Context? = null
fun onLoginButtonClicked(view: View){
if(username.isNullOrEmpty() || password.isNullOrEmpty()){
authListener?.onError("Invalid username or password")
isCredentialsValid = false
return
}
if(!username.isNullOrEmpty() && password!!.length >= 8){
isCredentialsValid = true
authListener?.onSuccess()
}else{
authListener?.onError("Invalid")
}
}}
Lets assume now that I enter an username and password and both meet the criteria. Now i'd like to, when i click on the "Log in" button, the current fragment is replaced by a menu fragment, for example.
How could i achieve something like that ? I've tried to replace from the ViewModel class, but that doesn't work.
Should I take the result of "isCredentialsValid" from the VM class and respond accordingly in the LoginFragment class ?
Thank you.
You have to use live data for updating the data from viewModel to view. I will post the code how it should be, but make sure that you need to understand the concept of LiveData.
LoginViewModel.kt
class LoginViewModel: ViewModel(){
var username: String? = null
var password: String? = null
var isCredentialsValid: Boolean = false
var authListener: AuthListener? = null
private val context: Context? = null
// LiveData to udpate the UI
private val _isValidCredential = MutableLiveData<Boolean>()
val isValidCredential: LiveData<Boolean> = _isValidCredential
fun onLoginButtonClicked(view: View){
if(username.isNullOrEmpty() || password.isNullOrEmpty()){
authListener?.onError("Invalid username or password")
isCredentialsValid = false
return
}
if(!username.isNullOrEmpty() && password!!.length >= 8){
isCredentialsValid = true
// to update the value of live data wherever you need
_isValidCredential.value = true
authListener?.onSuccess()
}else{
authListener?.onError("Invalid")
// to update the value of live data wherever you need
_isValidCredential.value = false
}
}
}
Your Fragment should be
LoginFragment.kt
class LoginFragment: Fragment(), AuthListener {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<CredentialsLoginFragmentBinding>(
inflater,
R.layout.credentials_login_fragment,
container,
false
)
val viewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
val view: View = binding.root
val registerButton: Button = view.findViewById(R.id.register_button)
binding.viewModel = viewModel
viewModel.authListener = this
// This is the way you need to observe the value
viewModel.isValidCredential.observe(viewLifecycleOwner, Observer {
if(it){
// do your navigation stuff here
}else{
// do your stuff if not valid credential
}
})
registerButton.setOnClickListener {
val transaction: FragmentTransaction? =
fragmentManager?.beginTransaction()
transaction?.replace(R.id.fragment_container, SignupFragment())?.commit()
}
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val constraintRoot: MotionLayout = view.findViewById(R.id.sign_in_root)
ActivityUtils().switchLayoutAnimationKeyboard(constraintRoot = constraintRoot)
}
override fun onStarted() {
Toast.makeText(context, "Started", Toast.LENGTH_SHORT).show()
}
override fun onSuccess() {
Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show()
}
override fun onError(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}}
A typical way of communicating back to the UI from the view model is using livedata. In your LoginViewModel, you would set your livedata to either true or false. Inside your view LoginFragment.kt you would have an observer. This observers job is to fire anytime a livedata's value has changed. That way you can have logic in your view that can either shows an error message liveData = false or launch the menu fragment = true.
Here is a good example using livedata to pass data to the view (fragment) this in the docs: https://developer.android.com/topic/libraries/architecture/viewmodel#implement
I would like to ask you for help. I am writing an application that uses MVVM and LiveData architecture. Inside ViewPager I have 3 fragments displaying data that comes from ViewModel. And I noticed that after connecting the viewModel to the activity and to the fragment, the data is updated only when the activity is started, but then Observe does not update the data even though the data has changed. After calling the next query to the server, inside onDataSet I send the appropriate time and obtains JSON data from the server, which it parses and passes to ViewModel. Why Fragment updates data only once in the beginning and nothing changes after?
This is the activity that hosts the fragments
class MainActivity : AppCompatActivity(), DatePickerDialog.OnDateSetListener {
private lateinit var currencyViewModel: CurrencyViewModel
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private lateinit var navigationView: NavigationView
private lateinit var floatingActionButton: FloatingActionButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fm = supportFragmentManager
currencyViewModel = ViewModelProvider
.AndroidViewModelFactory(application)
.create(CurrencyViewModel::class.java)
viewPager = findViewById(R.id.viewPager)
tabLayout = findViewById(R.id.tabLayout)
navigationView = findViewById(R.id.navigationView)
floatingActionButton = findViewById(R.id.floatingActionButton)
val viewPagerAdapter = CurrencyViewPagerAdapter(this)
viewPager.adapter = viewPagerAdapter
TabLayoutMediator(tabLayout
,viewPager
,TabLayoutMediator.TabConfigurationStrategy {
tab, position ->
when(position){
0 -> tab.text = "Tabela A"
1 -> tab.text = "Tabela B"
2 -> tab.text = "Tabela C"
}
}).attach()
floatingActionButton.setOnClickListener {
val dialog = CalendarFragment()
dialog.show(fm, "DatePickerDialog")
}
}
override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
//Convert year,month,day to millisecounds
val c = Calendar.getInstance()
c.set(year,month,dayOfMonth)
val dayInMillis = c.time.time
val today = Calendar.getInstance()
if(checkIsDateAfterToday(today, c)){
CoroutineScope(Dispatchers.Main).launch {
currencyViewModel.setTableA(dayInMillis)
}
}
}
This is ViewModel common for activity and fragment
class CurrencyViewModel : ViewModel() {
private val repository = CurrencyRepository()
val tableA: MutableLiveData<Array<TableA>> by lazy {
MutableLiveData<Array<TableA>>().also {
loadTableA(Date().time)
}
}
private fun loadTableA(time: Long) {
CoroutineScope(Dispatchers.Main).launch {
val loadedData = CoroutineScope(Dispatchers.IO).async {
repository.getTableA(time)
}.await()
tableA.value = loadedData
}
}
fun setTableA(time: Long){
loadTableA(time)
}
}
And that's the fragment which displays data in recyclerView
class TableAFragment : Fragment() {
private lateinit var currencyViewModel: CurrencyViewModel
private lateinit var recyclerViewA: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_table_a, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
currencyViewModel = ViewModelProvider.AndroidViewModelFactory
.getInstance(requireActivity().application)
.create(CurrencyViewModel::class.java)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerViewA = view.findViewById(R.id.recyclerView_A)
recyclerViewA.layoutManager = LinearLayoutManager(requireContext())
currencyViewModel.tableA.observe(viewLifecycleOwner, androidx.lifecycle.Observer{
val nbpAdapter = NBPAdapter(it)
recyclerViewA.adapter = nbpAdapter
})
}
}
Your instantiation of ViewModel is incorrect.
Should be
currencyViewModel = ViewModelProvider(this).get<CurrencyViewModel>() // lifecycle-ktx
and in Fragment:
currencyViewModel = ViewModelProvider(requireActivity()).get<CurrencyViewModel>() // lifecycle-ktx