Create a button programmatically in a fragment using Kotlin - android

I wish to generate a column of buttons inside a fragment in Kotlin from a database. For now I tried with just one button, but I cannot seem to do it without hardcoding it in the XML file. Here is my code so far:
class NotesFragment() : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_notes, container, false)
//val root : ViewGroup = inflater.inflate(R.layout.fragment_notes, null) as ViewGroup
val button_Id: Int = 1111
val button = Button((activity as MainActivity?)!!)
button.setText("Button 4 added dynamically")
button.setLayoutParams(ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
button.setId(button_Id)
button.x = 250f
button.y = 500f
button.setOnClickListener(this)
view.add(button)
return view
}
}
I know I should probably look for the layout somewhere in this and do an addView with it... What is the next step?

I did it like this
view.rootView.findViewById<RelativeLayout>(R.id.button_container).addView(button)

Related

Android Studio: Set Text in fragment

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 .

How to use ViewBinding when referencing view IDs from "other" layout XML i.e, not the corresponding Fragment XML?

I was previously using Kotlin Synthetics.
Here are the relevant files:
view_error.xml (other layout XML)
RecipeDetailFragment.kt
fragment_recipe_detail.xml (corresponding Fragment XML)
Previous Code in short (Using Kotlin Synthetics)
import kotlinx.android.synthetic.main.view_error.*
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_recipe_detail, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
// btnRetry is from view_error.xml. Accessed using Kotlin Synthetics
btnRetry.setOnClickListener {
viewModel.retryRecipeRequest(args.id)
}
}
Current Code Attempt in short: (Using ViewBinding)
So, here I successfully used ViewBinding for corresponding Fragment layout.
But I am not sure how to use ViewBinding for view_error.xml here to access btnRetry of view_error.xml?
What code is required to be added below?
import com.packagename.databinding.FragmentRecipeDetailBinding
private var _binding: FragmentRecipeDetailBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentRecipeDetailBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
// NOW THE btnRetry gives error as I removed the kotlin synthetics imports.
// How to access btnRetry through ViewBinding?
btnRetry.setOnClickListener {
viewModel.retryRecipeRequest(args.id)
}
}
You must be using <include> element to use the external layout within fragment_recipe_detail. something like this
in fragment_recipe_detail.xml
<include
android:id="#+id/retryLayoutId"
layout="#layout/retryLayout"
/>
So now in the case of view binding you can access the viewbinding variable and access the external layout id and then access its children. Something like given below.
binding.retryLayoutId.btnRetry.setOnClickListener {
viewModel.retryRecipeRequest(args.id)
}
where layoutId is the included layout's id.

Can't get access to Views by DataBinding or Android Extensions

I can't change value of Views by DataBinding or by Android Extensions, but it works by 'traditional way' (findViewById). Also, it throws NPE when I try by Android Extensions' way without safe call operator.
HourlyFragment.kt
class HourlyFragment : Fragment() {
private lateinit var binding: FragmentHourlyBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
var rootView = inflater.inflate(R.layout.fragment_hourly, container, false)
binding = DataBindingUtil.inflate(
inflater, R.layout.fragment_hourly, container, false
)
// doesn't work
//binding.tvHourly.text = "HOURLY!"
val tvHourly : TextView = rootView.findViewById(R.id.tv_hourly)
tvHourly.setText("HOURLY!!")
// doesn't work, without safe call operator throws NullPointerException
//tv_hourly?.text = "HOURLY!!!"
return rootView
}
}
fragment_hourly.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.HourlyFragment">
<TextView
android:id="#+id/tv_hourly"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="hourly fragment" />
</FrameLayout>
</layout>
Currently you are inflating your views twice and that's causing all the issues I believe, so instead just inflate your views once and see if it solves your issue
Try the following
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// var rootView = inflater.inflate(R.layout.fragment_hourly, container, false)
binding = DataBindingUtil.inflate(
inflater, R.layout.fragment_hourly, container, false
)
binding.tvHourly.text = "HOURLY!"
// val tvHourly : TextView = rootView.findViewById(R.id.tv_hourly)
tvHourly.setText("HOURLY!!")
return binding.root
}
Why your data binding doesn't work:
You are manually inflating with the line
var rootView = inflater.inflate(R.layout.fragment_hourly, container, false)
but then you use data binding to inflate another copy of this hierarchy in the next line of code. You then return the first layout from onCreateView, throwing away your second inflated data binding layout. You changed the text of the view in the data binding layout that you then threw away.
Why your Android Extensions doesn't work:
You can't use the synthetic view references before onCreateView() returns, because the Fragment still doesn't have any view attached to it for the synthetic property to use to find the child view.
You should be modifying view content in onViewCreated() instead.
By the way, Android Extensions is deprecated and should no longer be used.
In a more concise and more readable way:
private lateinit var binding: FragmentHourlyBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentHourlyBinding.inflate(
inflater, container, false
).apply {
tvHourly.text = "HOURLY!"
}
return binding.root
}

How to fetch resource id in fragment using kotlin in android?

I tried to this code which is mention below, but getting crash during run time. The error occurred is Android Run time:
FATAL EXCEPTION: main Process: com.root.specialbridge, PID: 17706 kotlin.KotlinNullPointerException at com.root.specialbridge.fragments.profile_fragment.WallFragments.initializeView(WallFragments.kt:49)
class WallFragments : Fragment(){
private var wallAdapter: WallAdapter? = null
private var wall_recycler: RecyclerView? = null
private val wallArrayList: ArrayList<Wall>? = null
private var mainlayout: LinearLayout? = null
private var no_result_found_layout: RelativeLayout? = null
private var userProfileWallInterface: UserProfileWallInterface? = null
internal var wallActivityBeanse: MutableList<WallActivityBeans> = ArrayList()
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater!!.inflate(R.layout.wall_fragments, container, false)
userProfileWallInterface = UserProfileWallPresentation(activity, this)
initializeView()
wallAdapter = WallAdapter(activity, wallActivityBeanse)
wall_recycler!!.adapter = wallAdapter
return view
}
fun initializeView() {
wall_recycler = view!!.findViewById(R.id.wall_recycler_id) as RecyclerView
mainlayout = view!!.findViewById(R.id.mainlayout) as LinearLayout
no_result_found_layout = view!!.findViewById(R.id.no_result_found_layout) as RelativeLayout
wall_recycler!!.layoutManager = LinearLayoutManager(activity)
wall_recycler!!.setHasFixedSize(true)
if (AuthPreference(activity).isGetMemberProfile) {
userProfileWallInterface!!.getMemberProfileWall(view!!)
} else {
userProfileWallInterface!!.getUserProfileWall(AuthPreference(activity).token, AuthPreference(activity).user.id, view!!)
}
}
companion object {
val instance: WallFragments
get() = WallFragments() }}
Add
apply plugin: 'kotlin-android-extensions'
in the app level gradle file and
import
import kotlinx.android.synthetic.main.fragment_your_fragment_name.view.*
in the onCreateView of your fragment (e.g. if the id of your textview is textView)
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view = inflater!!.inflate(R.layout.fragment_splashfragment, container, false)
view.textView.text = "hello" //add your view before id else getting nullpointer exception
return view
}
UPDATE:
declare viewOfLayout in your class instead of view.
class yourfragment:Fragment(){
private lateinit var viewOfLayout: View
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
viewOfLayout = inflater!!.inflate(R.layout.fragment_splashfragment, container, false)
viewOfLayout.textView.text = "hello" //add your view before id else will get nullpointer exception
return viewOfLayout
}
}
Introducing Kotlin Android Extensions.
You do not have to use findViewById anymore. Using this plugin you can use the UI component directly as a global field. Supported in Activities, fragments and views.
Example,
To refer text view from the layout below,
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/hello"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"/>
</android.support.constraint.ConstraintLayout>
in activity you can simply write,
// Using R.layout.activity_main from the main source set
import kotlinx.android.synthetic.main.activity_main.*
class MyActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Instead of findViewById(R.id.hello) as TextView
hello?.setText("Hello, world!")
}
}
in fragments,
// Using R.layout.fragment_content from the main source set
import kotlinx.android.synthetic.main.fragment_content.*
class ContentFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_content, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Instead of view.findViewById(R.id.hello) as TextView
hello?.setText("Hello, world!")
}
}
and for views,
// Using R.layout.item_view_layout from the main source set
import kotlinx.android.synthetic.main.item_view_layout.*
class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindData(data: String) {
// Instead of itemView.findViewById(R.id.hello) as TextView
itemView.hello?.setText(data)
}
}
And, you should not use !! everywhere, unless you want NullPointerException explicitly.
Instead use anyone from the following:
Do null check with safe call - ?., Eg. nullableVariable?.method()
Use non-null object using ?.let{ }, Eg. nullableVariable?.let { it.method() }
Supplying a backup value for the nullable variable using elvis operator - ?:, Eg. nullableVariable ?: <backupValue>.
Read more about Null Safety in Kotlin.
Initialization of view in fragment :
wall_recycler=view.findViewById<RecyclerView>(R.id.wall_recycler_id)
mainlayout = view.findViewById<LinearLayout>(R.id.mainlayout)
The problem is that you are accessing it too soon. requireView() and view returns null in onCreateView.I have find all views in onViewCreated().
Try doing it in the onViewCreated method:
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
wall_recycler=requireView().findViewById<RecyclerView>(R.id.wall_recycler_id)
mainlayout = requireView().findViewById<LinearLayout>(R.id.mainlayout)
mainlayout.setOnClickListener { Log.d(TAG, "onViewCreated(): hello world");}
}

Caused by: kotlin.TypeCastException: null cannot be cast to non-null type android.widget.TextView in fragments

class DashBoardFragment : Fragment() {
private var recyclerView : RecyclerView?=null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
var view = inflater!!.inflate(R.layout.fragment_dash_board, container, false)
initComponents(view )
return view
}
private fun initComponents(View view) {
var textView : TextView = view?.findViewById(R.id.recyclerView) as TextView
textView.setText("hello world")
}
when changing into recycler its showing same problem appreciate if you help to me
Well, how about adding more details. Like, what are you trying to accomplish, how are you doing it, what difficulties you have found with the approach, another approaches you have tried, etc. In summary, without code isn't a good question neither is with just code. Search for the balance!
Try moving the code in the initComponents function to the onCreateView function. The error might occur because recyclerView id is not ready until the View is created.
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
var view = inflater!!.inflate(R.layout.fragment_dash_board, container, false)
var textView : TextView = view?.findViewById(R.id.recyclerView) as TextView
textView.setText("hello world")
return view
}

Categories

Resources