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.
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 .
Is there a more generic way to init this two initialization lines?
private var _binding: MyFragmentViewBinding? = null
private val binding get() = _binding!!
Should we call every time binding
binding.cancelButton.setOnClickListener { }
binding.homeButton.setOnClickListener { }
binding.aboutButton.setOnClickListener { }
Or to create class variable?
cancelButton = binding.cancelButton
binding.cancelButton.setOnClickListener{}
And, should we set binding = null in adapter?
I think it is more of a personal preference. I like to do it with extension and higher-order function
fun <T : ViewDataBinding> Fragment.getDataBinding(layout: Int, container: ViewGroup?): T {
val binding: T = DataBindingUtil.inflate(layoutInflater, layout, container, false)
binding.lifecycleOwner = viewLifecycleOwner
return binding
}
My Fragment looks like this.
class InviteFragment : Fragment() {
private lateinit var binding: FragmentInviteBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = getDataBinding(R.layout.fragment_invite, container)
return binding.apply {
inviteAll.setOnClickListener(onInviteAllClick)
// You can set as many click listener here
// or some initialization related to view such as
// setting up recycler view adapter and decorators
}.root
}
private val onInviteAllClick = View.OnClickListener {
// Invite users
}
}
By doing things like this your onCreateView will be more readable and never going to get very long.
I will try to explain my issue step by step.
I created data variable within xml file
<data>
<variable
name="SLC90RViewModel"
type="com.example.poy.ui.questionnaires.tools.SCL90RViewModel"/>
</data>
In my ViewModel i need to retrieve string array from resources (which is stored in xml file), so i passed app context into ViewModel constructor. Now it looks like this:
class SCL90RViewModel(val context: Context) : ViewModel() {
val item = context.getResources().getStringArray(R.array.firstArray)
}
Finally, in my Fragment i want to assign LifecycleOwner and ViewModel to the binding:
class QuestionnaireFragment : Fragment(R.layout.fragment_questionnaire) {
private var binding: FragmentQuestionnaireBinding? = null
private val scl90rViewModel: SCL90RViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val fragmentBinding = FragmentQuestionnaireBinding.inflate(inflater, container, false)
binding = fragmentBinding
return fragmentBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
SCL90RViewModel = scl90rViewModel
}
}
}
However, SCL90RViewModel in binding?.apply{} is outlined with red and message "Classifier 'SCL90RViewModel' does not have a companion object, and thus must be initialized here" pops up. How can i fix it?
SCL90RViewModel is ambiguous here, it can be the binding variable or the ViewModel class. So use this inside the apply block to refer to the binding variable.
binding?.apply {
lifecycleOwner = viewLifecycleOwner
this.SCL90RViewModel = scl90rViewModel
}
Inside my fragment, i have some image and views that are getting their values by binding data, and beneath them a RecyclerView. The images and textviews are showing successfully but my Recyclerview won't show. If i return my view only, the RecyclerView shows but the binded data doesn't. I want to view both of them.
[]
class DetailFragment : Fragment(), LessonRecyclerAdapter.LessonItemListener {
private lateinit var viewModel: SharedViewModel
private lateinit var recyclerView: RecyclerView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_detail, container, false)
recyclerView = view.findViewById(R.id.lessonRecyclerView)
navController = Navigation.findNavController(requireActivity(), R.id.nav_host )
viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
viewModel.lessonData.observe(viewLifecycleOwner, Observer {
val adapter =
LessonRecyclerAdapter(
it,
this
)
recyclerView.adapter = adapter
})
// return binding data
val binding = FragmentDetailBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
//return view
}
As you can see, There are two inflates, one for binding and another for view(for recyclerview setup). The easy solution is to directly use recyclerview from binding variable to set up the list as:
private lateinit var binding: FragmentDetailBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
navController = Navigation.findNavController(requireActivity(), R.id.nav_host )
viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
binding = FragmentDetailBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
binding.viewModel = viewModel
viewModel.lessonData.observe(viewLifecycleOwner, Observer {
val adapter =
LessonRecyclerAdapter(
it,
this
)
// directly access the view using ids
binding.lessonRecyclerView.adapter = adapter
})
return binding.root
}
Another option is to use binding adapters with live data to setup adapter and pass the data to the adapter.
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");}
}