why onClickListener does not work in my fragment activity? - kotlin

Im new in programming, and i got stuck with adding onClickListener in my FragmentHome.kt
i added this code to my existing activity:
val exc = this.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
I tried set onClicklistener on a blank activity and it worked, but when i added it to an existing
Fragment activity it does nothing (it should display a toast with some text)
I see no error messages so i don't know where the problem could be.
Thank you for your responses.
enter code here
public class FragmentHome : Fragment() {
public class HomeFragmentElements : AppCompatActivity() {
private lateinit var spinView: Spinner
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_home)
val exc = this.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
spinView = findViewById(R.id.spinner)
spinView.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
TODO("Not yet implemented")
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
}
enter code here

Try to put your code into "onViewCreated":
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val exc = this.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
}
onViewCreated is executed after "onCreateView". View bindings and initializations should be into onViewCreated

change the following in your onCreateView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
collectId(view)
return view
}
fun collectId(view: view){
val exc = view.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
}
You can consider viewBinding instead of findViewById

It looks like you have defined an AppCompatActivity subclass inside your Fragment class's definition, kind of like this:
class MyFragment: Fragment {
class MyActivity: AppCompatActivity() {
//...
}
}
The ability to define a non-inner class in a nested way is purely a code organization tool. There is absolutely no connection between your Fragment and Activity classes here. The code above is no different than if you did this, with each class in a completely different file:
class MyFragment: Fragment {
}
class MyActivity: AppCompatActivity() {
}
It also doesn't make sense to think of an Activity as a child of a Fragment. A Fragment is a piece of an Activity, so it's the other way around.
All the code that you have in this Activity's onCreate should be put in the Fragment's onViewCreated() function, except for the call to setContentView(), because that's what your overriding of onCreateView() does. And remove the unused nested Activity class.
Also, you don't need to override onCreateView if you're simply inflating a layout and returning it. You can put the layout directly in the super-constructor call like this:
public class FragmentHome : Fragment(R.layout.fragment_home) {
private lateinit var spinView: Spinner
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// your view setup code
}
}

Related

RecyclerView adapter doesnt execute any methods

I am working on a simple project. I am trying to show list of data but in adapter none of the methods are called
class PlacnikiAdapter(private val placniki: List<Placnik>) :
RecyclerView.Adapter<PlacnikiAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView
init {
// Define click listener for the ViewHolder's View.
textView = view.findViewById(R.id.ime)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(viewGroup.context).inflate(R.layout.layout_rv_item,
viewGroup, false)
return ViewHolder(inflater)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = placniki[position].ime
}
override fun getItemCount(): Int {
return placniki.size
}
class PlacnikiFragment : Fragment() {
private val TAG = "PlacnikiFragment"
private lateinit var binding: PlacnikiFragmentBinding
lateinit var viewModel: MainViewModel
private val retrofitService = RetrofitService.getInstance()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Defines the xml file for the fragment
return inflater.inflate(R.layout.placniki_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = PlacnikiFragmentBinding.inflate(layoutInflater)
//get viewmodel instance using MyViewModelFactory
viewModel =
ViewModelProvider(this, ViewModelFactory(Repository(retrofitService))).get(
MainViewModel::class.java
)
//set recyclerview adapter
viewModel.placnikiList.observe(viewLifecycleOwner, {
Log.d(TAG, "placnikiList: $it")
binding.recyclerview.adapter = PlacnikiAdapter(it)
})
viewModel.errorMessage.observe(viewLifecycleOwner, {
Log.d(TAG, "errorMessage: $it")
})
viewModel.getVsiPlacniki()
}
I dont know what could be causing this. I changed activity to a fragment and beforehand everything worked normally and after changing to fragment recyclerview isnt showing items and the list isnt empty either so i dont pass list of zero items
I found the solution, the problem was in data binding. I had to add below code in gradle module
buildFeatures{dataBinding = true}

Activities vs Fragments for apps

I have been trying to figure this out for a bit now. I see a lot of developers and youtubers (tutorials) saying that we should use as little activities as possible in order for a faster, more efficient and less resource heavy code/app. I was wondering if there is a way to create a Log-in and Sign-up using only the MainActivity for both Log-in and Sign-Up in combination with fragments for navigation between them.
Or
Do we need atleast 2 or more activities to handle that process ( Log-in & Sign-Up )?
Example: 1 activity for log-in and 1 activity for sign-up.
Appreciate and welcome any answers regarding this topic!
Theoretically, you could have your entire application run on a single Activity and use Fragments for all of the pages.
Each fragment has its own lifecycle within the activity.
MainActivity can look like this
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadFragment(2)
}
public fun loadFragment(page: Int){
if(page == 1){
// load login
val manager = supportFragmentManager.beginTransaction()
manager.replace(R.id.fragment_holder, LoginFragment()).commit()
}else{
// load register
val manager = supportFragmentManager.beginTransaction()
manager.replace(R.id.fragment_holder, LoginFragment()).commit()
}
}
}
LoginFragment can look like this
class LoginFragment : Fragment() {
lateinit var myButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_login, container, false)
myButton = view.findViewById(R.id.my_button)
myButton.apply {
setOnClickListener { toRegister() }
}
return view;
}
fun toRegister(){
// replace the fragment in main activity with register fragment
(requireActivity() as MainActivity).loadFragment(1)
}
}
RegisterFragment can look like this
class RegisterFragment : Fragment() {
lateinit var mButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_register, container, false)
mButton = view.findViewById(R.id.my_button)
mButton.apply {
setOnClickListener { toLogin() }
}
return view;
}
fun toLogin(){
// replacing the fragment in the main activity with the login fragment
(requireActivity() as MainActivity).loadFragment(1)
}
}
Basically, we replace the fragment displayed in the activity by calling loadfragment.
This logic can be applied to as many fragments as is necessary.

Calling method from Fragment in RecyclerView Adapter - Lateinit property textView has not been initialized

I have a list of items. When I swipe on an item, I want to update a textView in the Fragment.
I use a RecyclerView. In the Adapter, I detect when a user swipes something, and then I try to call a method in the Fragment to update a textview.
However, when I try it crashes with "LateInit Propery TextView has not been initialized". I have tried initializing the textView in the adapter instead, but I don't think that's correct and it seems impossible as findViewById doesn't really work in an adapter since the specific TextView is not on an item layout, but instead displayed in the Fragment Layout (and it needs to be that way as I don't want the textview on every single item).
My adapter (simplified)
class SwipeAdapter internal constructor(private val mCandidatesArrayList: ArrayList<SwipeCandidate>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mSwipeFragment = SwipeFragment() //Here I initialize my fragment
fun createHelperCallback(): ItemTouchHelper.Callback {
return object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
val position = viewHolder.bindingAdapterPosition
mSwipeFragment.updateTextView() //This throws the error
}
}
}
}
}
And my Fragment (simplified):
class SwipeFragment : Fragment() {
private lateinit var textView : TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
textView = root.findViewById(R.id.textview) //initializing textview
}
fun updateTextView() {
textView.setText("Hello") //Setting the text
}
}
You are trying to get view from a fragment that is not open. In our fragment, as I understand it, the adapter is initialize, you need to pass a listener into it, which will be called after the swipe.
class SwipeAdapter internal constructor(var onSwiped: (position: Int) -> Unit = { }, private val mCandidatesArrayList: ArrayList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun createHelperCallback(): ItemTouchHelper.Callback {
return object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder,
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
val position = viewHolder.bindingAdapterPosition
onSwiped(position)
}
}
}
}
fragment
class SwipeFragment : Fragment() {
private lateinit var textView: TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
textView = root.findViewById(R.id.textview)
val adapter = SwipeAdapter({ position: Int ->
textView.setText("Hello")
}, arrayListOf())
}
}

Recycler View and DialogFragment struggling (Kotlin)

Hello i am using a recyclerView to swipe and call for a Dialog fragment
The the idea is that when i dismiss the dialog Fragment the raw come back to the original place
but the problem is that it return, just after the dialog is opened.
So i do not how to call notifyItemChanged inside the dialogFragment, or implement the dialogFragment.setOnDismissListener because when i override i dont know how to pass to the function the adapter to call notifyItemChanged i
This is my code
class Fragmento : Fragment(), RecyclerAdapter.ClickListener {
// TODO: Rename and change types of parameters
private lateinit var adapter :RecyclerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view =inflater.inflate(R.layout.fragment_fragmento, container,false)
initRecyclerView(view)
return view
}
private fun initRecyclerView(view: View) {
val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager=LinearLayoutManager(activity)
adapter = RecyclerAdapter()
recyclerView.adapter=adapter
val itemSwipe=object:ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT){
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
showDialog(viewHolder as RecyclerAdapter.ViewHolder)
var dialog = DialogFragment()
dialog.show(childFragmentManager,"dialog")
adapter.notifyItemChanged(viewHolder.adapterPosition)
}
}
val swap =ItemTouchHelper(itemSwipe)
swap.attachToRecyclerView(recyclerView)
}
private fun showDialog(viewHolder: RecyclerAdapter.ViewHolder){
val builder= AlertDialog.Builder(activity)
builder.setTitle("DeleteItem")
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance() =
Fragmento().apply {
arguments = Bundle().apply {
}
}
}
}
and for the DialogFragment
class DialogFragment: DialogFragment(){
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var rootView: View = inflater.inflate(R.layout.dialog_fragment_noticias, container, false)
return rootView
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
}
}
If I have understand the problem here is that you want to call notifyItemChanged() when your dialog is dismiss without setOnDismissListener.
If you want to do that you can simply add a callback on your DialogFragment and use it in your recyclerView.
class DialogFragment(dismiss: () -> Unit): DialogFragment(){
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var rootView: View = inflater.inflate(R.layout.dialog_fragment_noticias, container, false)
return rootView
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
dismiss()
}
}
When you are creating the DialogFragment you can use the callback :
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
showDialog(viewHolder as RecyclerAdapter.ViewHolder)
var dialog = DialogFragment() {
//here you are notified when the dialog is dismiss
adapter.notifyItemChanged(viewHolder.adapterPosition)
}
dialog.show(childFragmentManager,"dialog")
adapter.notifyItemChanged(viewHolder.adapterPosition)
}

Layout view is not initialized with Butterknife

In the following code:
class LobbyFragment : Fragment() {
#Inject
lateinit var lobbyFragmentHelloService: LobbyFragmentHelloService
#BindView(R.id.lobby_fragment_hello)
lateinit var lobbyFragmentHelloTextView: TextView
lateinit var unbinder: Unbinder
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.lobby_fragment, container, false)
unbinder = ButterKnife.bind(this, view)
return view
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sayFragmentHello()
}
override fun onAttach(context: Context?) {
AndroidInjection.inject(this)
super.onAttach(context)
}
override fun onDestroyView() {
super.onDestroyView()
unbinder.unbind()
}
private fun sayFragmentHello() {
lobbyFragmentHelloTextView.text = lobbyFragmentHelloService.sayHello()
}
}
lobbyFragmentHelloTextView is never initialized. Butterknife is used to initialize this variable. Why is not initialized by the time sayFramentHello is called?
I'm not really sure what went wrong but to fix the issue, you can consider using kotlin built in synthetic binding and just get rid of butterknife. It's more efficient.
explained here