My Variable Class
data class QnaVariable( val questionOne: String, val answerOne : String, val questionTwo: String, val answerTwo : String)
My ViewModel Class
val item = QnaVariable("I am unable to login my account",
"You can reset password using the Reset Password in My account Page. If you are still unable to access your account, then please call our customer care.",
"Still need help?",
"Have a queries? please get in touch and we will be happy to help you")
}
My Fragment Class
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentQuestionAnswerBinding.inflate(layoutInflater, container, false)
val model = viewModel.item
//binding.questionHeading1.text = model.toString()
binding.questionHeading1.text = QNAdapter(myList).toString()
return binding.root
}
}
return FragmentQuestionAnswerBinding.inflate(layoutInflater, container, false).apply {
binding = this
//TODO: write code to update your ui
}.root
Modify binding part like this.
Related
I've recently picked up an Android Studio project I started a year ago in Kotlin.
It features three fragments that can be navigated through by a bottom navigation bar.
Now, to break my current issue down to a simple example that even doesn't work for me:
Given there's a the editText object exercise in fragment_home.xml and I want to call and alter it in HomeFragment.kt.
I checked every source of advice I could find from Google & Stackoverflow and came up with the following code in HomeFragment.kt (partially pre-coded by AndroidStudio):
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root
val view: View = inflater!!.inflate(R.layout.fragment_home,container,false)
view.exercise.setText("This is an exceptionally hardcoded string")
The last line stands for every object I tried to reach. I also tried onClickListening for buttons like so:
val btnNewExercise = view.findViewById<Button>(R.id.btn_new_exercise)
btnNewExercise.setOnClickListener {view
Toast.makeText(view.context, "New exercise will be generated", Toast.LENGTH_SHORT).show()
println("Generated a new exercise")
}
but nothing happens when I start the app/ hit the buttons - I seem to can't get through to the actual view's objects to access them. Even ran into NullPointerExceptions on my way to a solution.
I could supply the fragment and layout files if needed - just thought this way it might be easier at first.
If anybody could tell me where I'm wrong I'd be really grateful! Thanks in advance!
You inflated the layout twice.
Remove this. You already inflated the layout using view binding in the FragmentHomeBinding.inflate... call
val view: View = inflater!!.inflate(R.layout.fragment_home,container,false)
and replace
view.exercise.setText("This is an exceptionally hardcoded string")
with (using binding
binding.exercise.setText("This is an exceptionally hardcoded string")
then the last line on your onCreateView should be
return binding.root
Note: You should have these class properties:
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
So it will look like this:
//move your view model as a class property so it will be accessible by other class methods
private val homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!//transform to immutable
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
//use the immutable view binding property
binding.exercise.setText("This is an exceptionally hardcoded string")
return binding.root
}
For more info, read view binding
Try this solution.
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.exercise.text="This is an exceptionally hardcoded string"
binding.btnNewExercise.setOnClickListener {
Toast.makeText(view.context, "New exercise will be generated", Toast.LENGTH_SHORT).show()
println("Generated a new exercise")
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
You need to return the view you made in the OnCreateView .
Here is my code on the sender fragment
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_list_card,container,false)
rcvAdapter = CardAdapter(cardList,this)
view.cardRecyclerView.layoutManager = LinearLayoutManager(container?.context,LinearLayoutManager.VERTICAL,false)
view.cardRecyclerView.adapter = rcvAdapter
return view
}
override fun onClick(card: Card) {
val cardBundle = Bundle()
cardBundle.putSerializable("Card",card)
val infoFrag = CardInfoFrag()
infoFrag.arguments = cardBundle
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.fragmentContainer,infoFrag)
transaction?.commit()
}
Here is the code on the receiver
class CardInfoFrag : Fragment() {
private val card : Card? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bundle = Bundle(arguments)
val card : Card = bundle.getSerializable("Card") as Card
Log.d("CARDINFO: ",card.name+" "+ card.cardDate+" "+card.cardNum)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
cardNameInfo.text = card?.name
cardExDateInfo.text = card?.cardDate
cardNumInfo.text = card?.cardNum
return inflater.inflate(R.layout.fragment_card_info,container,false)
}
}
The cardList is declared as ArrayList<Card>
The Card object
class Card (val cardNum : String, val cardDate : String, val name : String) :Serializable{
}
The logcat, when I attempt to create a new fragment with data from the passed object, says my cardNameInfo.text can't be null while the log command clearly says I received the object and its data. I can't work out what may cause the problem
Any tip is appreciated
As its name implies, onCreateView get called before the fragment view is created; So within the onCreateView, you need to explicitly use the view that will be returned by it to access any underlying views.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_card_info,container,false)
cardNameInfo = view.findViewById<TextView>(R.id.foo1)
cardExDateInfo= view.findViewById<TextView>(R.id.foo2)
cardNumInfo= view.findViewById<TextView>(R.id.foo3)
cardNameInfo.text = card?.name
cardExDateInfo.text = card?.cardDate
cardNumInfo.text = card?.cardNum
return view
}
Change the foo Ids to the corresponding ids in the fragment layout
Try this once, when you are getting the Bundle, the arguments is already a Bundle object. Maybe if you do something like this it might work: ( My answer is based off this answer :) )
class CardInfoFrag : Fragment() {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bundle: Bundle? = arguments
bundle?.let{
val card : Card = it.getSerializable("Card") as Card
Log.d("CARDINFO: ",card.name+" "+ card.cardDate+" "+card.cardNum)
}
}
...
}
What I've tried so far:
getString(R.id.editBrand) => this returns false (R.id.editBrand returns a long number)
view.findViewById(R.id.editBrand) => runs to nullreference
Any help would be vm appreciated, thank you!
My whole fragment:
class CreateFragment : Fragment() {
#SuppressLint("ResourceType")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val binding = DataBindingUtil.inflate<FragmentCreateBinding>(inflater,
R.layout.fragment_create,container,false)
val application = requireNotNull(this.activity).application
val dataSource = CarDatabase.getInstance(application).carDatabaseDao
val viewModelFactory = CarViewModelFactory(dataSource, application)
val carViewmodel =
ViewModelProvider(
this, viewModelFactory).get(CarViewmodel::class.java)
val adapter = CarAdapter()
binding.submitButton.setOnClickListener { view : View ->
view.findNavController().navigate(R.id.action_createFragment_to_readFragment)
carViewmodel.onCreated(12, view.findViewById<EditText>(R.id.editBrand).editBrand.toString(), "blue")
}
binding.setLifecycleOwner(this)
return binding.root
}
}
Use
binding.editBrand.text.toString()
Since binding is the reference to your layout here.
If you use view.findViewById<EditText>, view refers to the submitButton and calling findViewById on it will look for child views.
getString() take a string resource as a parameter and not a view resource.
My recyclerview doesn't udpate with the livedata when I remove an
item.
The function jokeisclicked() opens up a dialog for the user which
can choose to edit or delete an item of the room database.
The delete completes, but only when I refresh the tab. How can I complete the delete without refreshing the tab?
class DashboardFragment : Fragment() {
private lateinit var dashboardViewModel: DashboardViewModel
lateinit var binding: FragmentDashboardBinding
lateinit var adapter: JokeRecyclerViewAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//init repo and viewmodel
val dao: JokeDao = RoomDB.getInstance(requireContext()).JokeDAO
val jokerepo = JokeRepo(dao, RetrofitBuilder.jokeservice)
val factory = DashboardViewModelFactory(jokerepo)
dashboardViewModel = ViewModelProvider(this, factory).get(DashboardViewModel::class.java)
//init binding
binding = FragmentDashboardBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
//recycler observe from livedata
dashboardViewModel.jokes.observe(viewLifecycleOwner, {
binding.recyclerview.layoutManager = LinearLayoutManager(this.requireContext())
adapter = JokeRecyclerViewAdapter(it, { selected: Joke -> jokeIsClicked(selected) })
binding.recyclerview.adapter = adapter
})
return binding.root
}
fun jokeIsClicked(joke: Joke) {
//show pop up dialog
val dialog = PopUpFragment(joke)
getFragmentManager()?.let { dialog.show(it, "popUpDialog") }
}
}
here is the popupfragment class
class PopUpFragment(private val selectedJoke : Joke) : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
//init viewmodel
val dao = RoomDB.getInstance(this.requireContext()).JokeDAO
val jokerepo = JokeRepo(dao,RetrofitBuilder.jokeservice)
val factory = PopUpViewModelFactory(jokerepo)
val popupViewModel : PopUpFragmentViewModel = ViewModelProvider(this,factory).get(PopUpFragmentViewModel::class.java)
//init binding
val binding = FragmentPopUpBinding.inflate(inflater,container,false)
//edit clicked
binding.editjoke.setOnClickListener{
}
//delete clicked
binding.deletejoke.setOnClickListener {
//TODO
popupViewModel.deleteJoke(selectedJoke)
this.dismiss()
}
return binding.root
}
}
I think you dont update value of livedata at viewModel. Could you share viewmodles also ? If dialogViewModel removes joke from database than you have to return at #Dao's method LiveData or Flow.
Any way don't set adapert and layout manager at observe method - it's point less. For updating list at adaper you could create method and call in it notifiDataSetChanged() or (the best way of handling changes at adaper) diffUtils.
You'll need to do 2 things:
1 - Remove the data from your variable i.e if you're storying it within a an array then use array = ArrayUtils.remove(array, index);
2 - run .notifyDataSetChanged() on your RecyclerView's Adapter in order refresh the view based on the values of the new data set.
I want to pass some data from PowerofMind to wishlist fragment but I encountered with some error.
This Activity from where data has to be transferred
wish?.setOnClickListener({
val name = "Power of Subconcoius Mind"
val intent = Intent(this#PowerofMind, WishlistFragment::class.java)
intent.putExtra("Book: ", name)
startActivity(intent)
Toast.makeText(this, "Added to WishList", Toast.LENGTH_SHORT).show()
})
I want to show data in this activity as
class WishlistFragment : Fragment() {
var result: TextView? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_wishlist, null)
result =view.findViewById(R.id.list1)
val name = activity?.intent?.getStringExtra("Book: ")
list1.text = "Book: $name"
return view
}
}
But there is an error on Intent. Please Help
Here is an example how to instantiate a fragment with factory method:
companion object {
private const val MY_DATA_KEY = "my_data"
private const val ANOTHER_DATA_KEY = "another_data"
fun newInstance(mySerializableData: Any, anotherData: Int) = MyFragment().apply {
//bundleOf() is an exstension method from KTX https://developer.android.com/kotlin/ktx
arguments = bundleOf(MY_DATA_KEY to mySerializableData, ANOTHER_DATA_KEY to anotherData)
}
}
Here is how you can pass data between fragment in kotlin using Parcelable class:
on Button Click:
override fun onClick(v: View?) {
firstName = editTextName!!.text.toString()
lastName = editTextLast!!.text.toString()
Toast.makeText(context, firstName, Toast.LENGTH_SHORT).show()
// val viewFragment = ViewFragment()
// val transaction = fragmentManager.beginTransaction()
// transaction.replace(R.id.fragmentContainer, viewFragment)
// transaction.commit()
var details = Details(firstName!!, lastName!!)
val viewFragment = ViewFragment()
val bundle = Bundle()
bundle.putParcelable(KEY_PARSE_DATA, details)
viewFragment.setArguments(bundle)
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, viewFragment)
transaction.commit()
}
Here is an parcel class how to handle data
#Parcelize
class Details(val firstName: String, val lastName: String) : Parcelable
on another fragment
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_view, container, false)
textViewName = view.findViewById(R.id.text_name_another) as TextView
textViewLastName = view.findViewById(R.id.text_surname_another) as TextView
val bundle = arguments
if (bundle != null) {
val details = bundle.getParcelable<Details>(KEY_PARSE_DATA)
textViewName!!.setText(details.firstName)
textViewLastName!!.setText(details.lastName)
}
return view
}
Currently I don't know if this required or not in kotlin in app gradle (Check this before used)
androidExtensions {
experimental = true
}
In your WishlistFragment you are creating a new Intent instead of getting the one provided by the activity.
As you are using Kotlin, you can directly use intent instead of getIntent().
You could use also the synthetic advantage of kotlin, and drop that findViewById.
And as a tip, do not use string concatenation ;)
Your fragment would look something like the following:
class WishlistFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_wishlist, null)
val name = activity?.intent?.getStringExtra("Book: ")
many.text = "Book: $name"
}