Create a function for a button that is inside a fragment - kotlin

I've made an app with 4 fragments, each of which represents the app page. Now, inside one of the fragments I've got a ToggleButton. I am trying to get OnClickListener so that it changes the button's background colour once clicked. I'm coding this in my MainActivity.kt and not in the Fragments. However, it crashes the app without logcat.
package com.example.myassignment
import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.example.myassignment.Fragments.DestinationsFragment
import com.example.myassignment.Fragments.HelpFragment
import com.example.myassignment.Fragments.HomeFragment
import com.example.myassignment.Fragments.SettingsFragment
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_settings.*
class MainActivity : AppCompatActivity() {
private val helpFragment = HelpFragment()
private val settingsFragment = SettingsFragment()
private val homeFragment = HomeFragment()
private val destinationsFragment = DestinationsFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(homeFragment)
bottom_navigation.setOnNavigationItemSelectedListener{
when(it.itemId){
R.id.ic_home -> replaceFragment(homeFragment)
R.id.ic_destinations -> replaceFragment((destinationsFragment))
R.id.ic_help -> replaceFragment(helpFragment)
R.id.ic_settings -> replaceFragment(settingsFragment)
}
true
}
// This is the onclick for the button which is inside a fragment (SettingsFragment)
btnReset.setOnClickListener { resetColour() }
}
// Function for the onclick function
private fun resetColour() {
btnReset.setBackgroundColor(Color.parseColor("#3E3E3E"))
}
private fun replaceFragment(fragment: Fragment){
if (fragment !=null){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment)
transaction.commit()
}
}
}
And here's my SettingsFragment code (I haven't changed anything)
package com.example.myassignment.Fragments
import android.graphics.Color
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.myassignment.R
import kotlinx.android.synthetic.main.fragment_settings.*
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [SettingsFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class SettingsFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_settings, container, false)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment SettingsFragment.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
SettingsFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
Here's the XML for the fragment, the button I'm trying to change is btnReset
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".Fragments.SettingsFragment"
android:background="#color/black">
<TextView
android:id="#+id/txtSettings"
android:layout_width="323dp"
android:layout_height="102dp"
android:layout_marginTop="16dp"
android:background="#color/orange"
android:gravity="center"
android:text="Settings"
android:textAlignment="center"
android:textColor="#color/black"
android:textSize="60sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/txtNotifications"
android:layout_width="223dp"
android:layout_height="48dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:background="#color/grey"
android:gravity="center"
android:text="Notifications"
android:textAlignment="center"
android:textColor="#color/black"
android:textSize="20sp"
app:layout_constraintEnd_toStartOf="#+id/btnNotifications"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txtSettings" />
<TextView
android:id="#+id/txtNightMode"
android:layout_width="223dp"
android:layout_height="48dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:background="#color/grey"
android:gravity="center"
android:text="Night Mode"
android:textAlignment="center"
android:textColor="#color/black"
android:textSize="20sp"
app:layout_constraintEnd_toStartOf="#+id/btnNightMode"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txtNotifications" />
<TextView
android:id="#+id/txtWIFI"
android:layout_width="223dp"
android:layout_height="48dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:background="#color/grey"
android:gravity="center"
android:text="WI-FI Only"
android:textAlignment="center"
android:textColor="#color/black"
android:textSize="20sp"
app:layout_constraintEnd_toStartOf="#+id/btnWIFI"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txtNightMode" />
<ToggleButton
android:id="#+id/btnNotifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="44dp"
android:layout_marginRight="44dp"
android:text="ToggleButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txtSettings"
android:background="#color/cyan"
/>
<ToggleButton
android:id="#+id/btnNightMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="44dp"
android:layout_marginRight="44dp"
android:checked="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnNotifications"
android:background="#color/cyan"
/>
<ToggleButton
android:id="#+id/btnWIFI"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="44dp"
android:layout_marginRight="44dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnNightMode"
android:background="#color/cyan"/>
<Button
android:id="#+id/btnReset"
android:layout_width="337dp"
android:layout_height="154dp"
android:layout_marginTop="24dp"
android:text="Reset"
android:background="#color/cyan"
android:backgroundTint="#color/orange"
android:textColor="#color/black"
android:textSize="50sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txtWIFI" />
</androidx.constraintlayout.widget.ConstraintLayout>
----- Edit -----
New SettingsFragment code:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
btnReset.setOnClickListener { resetColour() }
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_settings, container, false)
}
private fun resetColour() {
btnReset.setBackgroundColor(Color.parseColor("3E3E3E"))
}
Here's the logcat
2021-02-17 08:20:32.594 11717-11717/com.example.myassignment E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.myassignment, PID: 11717
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
at com.example.myassignment.Fragments.SettingsFragment.onCreateView(SettingsFragment.kt:40)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2600)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:881)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:150)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

This line is your problem.
btnReset.setOnClickListener { resetColour() }
btnReset lives in your fragment, but you are trying to reference it from your activity. Try moving that code to the fragment which inflates the view that btnReset lives in.
See the line import kotlinx.android.synthetic.main.fragment_settings.* in your activity class? This is what made it seem like btnReset was a valid property to reference in your activity. You should delete that line.
Lastly, it's very unlikely that this 'crashed without logcat'. You probably have some sort of filter on your logcat that's preventing you from seeing the crash.

Related

Recycler View not displaying inside a Fragment

I'm using Recycler view to show items in my Fragment named Recent History. I want to display user data from firebase to my recent history Fragment. But the recycler view is not showing on the fragment. Please Help me out in this. What did I do wrong here? or what's the solution so that my fragment shows the recycler view.
Recent History Fragment Code:
`
class recent_history : Fragment(R.layout.fragment_recent_history) {
private var binding: FragmentRecentHistoryBinding? = null
private lateinit var auth: FirebaseAuth
var database: FirebaseDatabase? = null
var databaseReference: DatabaseReference? = null
private lateinit var userArrayList: ArrayList<User>
private lateinit var myAdapter: AdapterClass
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentRecentHistoryBinding.inflate(inflater, container, false)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
auth = FirebaseAuth.getInstance()
database = FirebaseDatabase.getInstance()
databaseReference = database?.reference!!.child("Users").child("result")
userArrayList= arrayListOf()
myAdapter=AdapterClass(userArrayList)
binding?.recyclerview.apply {
binding?.recyclerview?.adapter = myAdapter
var linearLayoutManager = LinearLayoutManager(activity)
binding?.recyclerview?.layoutManager = linearLayoutManager
binding?.recyclerview?.setHasFixedSize(true)
}
getData()
}
override fun onDestroy() {
super.onDestroy()
binding = null
}
private fun getData() {
val user = auth.currentUser
databaseReference?.child(user?.uid!!)
databaseReference!!.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
for (data in snapshot.children) {
var model = data.getValue(User::class.java)
userArrayList.add(model!!)
}
}
}
override fun onCancelled(error: DatabaseError) {
Log.e("cancel", error.toString())
}
})
}
}
`
Fragment Recent History Layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
tools:context=".recent_history">
<TextView
android:id="#+id/profiletitle"
android:layout_width="389dp"
android:layout_height="46dp"
android:layout_marginStart="4dp"
android:layout_marginTop="28dp"
android:fontFamily="monospace"
android:text="Recent Search History"
android:textAlignment="center"
android:textColor="#color/Twit"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</TextView>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="220dp"
android:layout_height="664dp"
android:layout_marginBottom="100dp"
android:orientation="vertical"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/profiletitle"
tools:listitem="#layout/adapterview">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
I'm sending data to firebase from another Fragment named keyword Fragment.
Code of keyword fragment:
class keywordfrag : Fragment() {
private var binding: FragmentKeywordfragBinding? = null
private lateinit var auth: FirebaseAuth
var database: FirebaseDatabase? = null
var databaseReference: DatabaseReference? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentKeywordfragBinding.inflate(inflater, container, false)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
auth = FirebaseAuth.getInstance()
database = FirebaseDatabase.getInstance()
val user = auth.currentUser
databaseReference?.child(user?.uid!!)
databaseReference = database?.getReference("Users")?.child("result")
binding?.analyzebtn?.setOnClickListener {
sendData()
}
}
override fun onDestroy() {
super.onDestroy()
binding = null
}
private fun sendData() {
val keywordtext = binding?.userenteredkeyword?.text.toString().trim()
val timestamp = Timestamp(System.currentTimeMillis())
val timendate = timestamp.toString().trim()
// val username=auth.currentUser?.uid.toString().trim()
// val email=auth.currentUser?.email.toString().trim()
if (TextUtils.isEmpty(keywordtext)) {
binding?.userenteredkeyword?.error = "keyword can not be Empty"
} else {
val model = User(keywordtext, timendate)
val user = auth.currentUser
databaseReference?.child(user?.uid!!)?.setValue(model)
binding?.userenteredkeyword?.setText("")
val currentUser = auth.currentUser
val currentUserdb = databaseReference?.child((currentUser?.uid!!))
currentUserdb?.child("keywordtext")?.setValue(keywordtext)
currentUserdb?.child("timendate")?.setValue(timendate)
}
}
}
Adapter Class:
package com.example.emotela_finalyearproject
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.emotela_finalyearproject.databinding.AdapterviewBinding
import kotlin.collections.ArrayList
class AdapterClass(var list:ArrayList<User>) :RecyclerView.Adapter<AdapterClass.ViewHolder>() {
class ViewHolder(val binding: AdapterviewBinding) : RecyclerView.ViewHolder(binding.root) {
var keyword = binding.keywordtv
var timenddate=binding.timendatetv
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = AdapterviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
with(holder) {
with(list[position]) {
keyword.text = this.keywordtext
timenddate.text= this.timendate
}
}
}
}
Adapter view Layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardElevation="8dp"
app:cardCornerRadius="8dp"
android:layout_margin="16dp">
<LinearLayout
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginVertical="6dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="336dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="#+id/keywordtitle"
android:layout_width="141dp"
android:layout_height="35dp"
android:fontFamily="monospace"
android:text="searched word: "
android:textSize="10sp"
android:textStyle="bold">
</TextView>
<TextView
android:id="#+id/keywordtv"
android:layout_width="143dp"
android:layout_height="36dp"
android:fontFamily="monospace"
android:textSize="10sp">
</TextView>
<TextView
android:id="#+id/timeanddatetitle"
android:layout_width="160dp"
android:layout_height="36dp"
android:fontFamily="monospace"
android:text="Time and Date: "
android:textSize="10sp"
android:textStyle="bold">
</TextView>
<TextView
android:id="#+id/timendatetv"
android:layout_width="171dp"
android:layout_height="48dp"
android:fontFamily="monospace"
android:textSize="10sp">
</TextView>
<LinearLayout
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginVertical="6dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="336dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
Result in Emulator:
You need to calll
imageAdapter.notifyDataSetChanged()
after you fetch the data from firebase.

why doesn't append function of TextView work in android studio in kotlin in customView

I was making a custom view to learn android studio. In the meantime, to check if my code work correctly, I write the following code. I expected
4layout_width=wrap_content
layout_height=wrap_content
id=#+id/customView
is written in textView but still 4 is been writing. what went wrong? is this android studio's error? To make correct code, how can I do?
custom.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.myapplication.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/customView"/>
</LinearLayout>
activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:id="#+id/root_layout">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textView"
android:text="4"/>
</LinearLayout>
MainActivity.kt
package com.example.myapplication
import android.content.Context
import android.content.Context.LAYOUT_INFLATER_SERVICE
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layout = findViewById<LinearLayout>(R.id.root_layout)
inflater.inflate(R.layout.custom, layout, true)
}
}
class MyView : View {
constructor(context : Context) : super(context)
constructor(context : Context, attr : AttributeSet?) : super(context, attr){
val inflater = context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layout = inflater!!.inflate(R.layout.activity_main, null)
val textView = layout!!.findViewById<TextView>(R.id.textView)
if(attr != null){
for(i in 0 until attr.attributeCount){
val string = "${attr.getAttributeName(i)}=${attr.getAttributeValue(i)}\n"
textView.append(string)
}
}
}
constructor(context : Context, attr : AttributeSet?, defStyleAttr : Int) :
super(context, attr, defStyleAttr)
}

RecyclerView doesn't appear on screen

The list doesn't display on screen and it doesn't throw any error.
Am I doing something wrong connecting the adapter and layoutmanager?
Whenever I open the fragment it writes the following line: "W/RecyclerView: No adapter attached; skipping layout" and it doesn't change anything to the fragment.
This is the MainActivity.kt:
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.ListView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity() {
val addnewmoviefragment = AddNewMovieFragment()
val movielistfragment = MovieListFragment()
val moviedetailsfragment = MovieDetailsFragment()
private var layoutManager: RecyclerView.LayoutManager? = null
private var adapter: RecyclerView.Adapter<MovieListViewAdapter.ViewHolder>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(movielistfragment)
val movies: MutableList<Movies> = initList()
layoutManager = LinearLayoutManager(this)
adapter = MovieListViewAdapter(this, movies)
val button = findViewById<BottomNavigationView>(R.id.bottom_navigation)
button.setOnNavigationItemSelectedListener {
when(it.itemId){
R.id.ic_movieadd -> replaceFragment(addnewmoviefragment)
R.id.ic_moviedetails -> replaceFragment(movielistfragment)
}
true
}
//val submitButton = findViewById<Button>(R.id.submitButton)
//val cancelButton = findViewById<Button>(R.id.cancelButton)
}
private fun replaceFragment(fragment: Fragment){
if(fragment!=null){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment)
transaction.commit()
}
}
private fun initList(): MutableList<Movies>{
return mutableListOf(
Movies(1, "Inception", "Very good movie!", "James Gun", Actors("Simeon")),
Movies(2, "Extracton", "Extracting!", "Christopher Nolan", Actors("Ivan"))
)
}
MovieListViewAdapter.kt
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.EditText
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class MovieListViewAdapter(val context: Context, val allMovies: MutableList<Movies>):
RecyclerView.Adapter<MovieListViewAdapter.ViewHolder>(){
class ViewHolder(view: View): RecyclerView.ViewHolder(view){
val id: TextView
val name: TextView
val description: TextView
val producer: TextView
val actors: TextView
init{
id = view.findViewById(R.id.movieId)
name = view.findViewById(R.id.movieNameId)
description = view.findViewById(R.id.movieDescriptionId)
producer = view.findViewById(R.id.movieProducerId)
actors = view.findViewById(R.id.movieActorsId)
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recycler_view_row, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentMovie: Movies = allMovies[position]
holder.id.text = currentMovie.id.toString()
holder.name.text = currentMovie.name
holder.description.text = currentMovie.description
holder.producer.text = currentMovie.producer
holder.actors.text = currentMovie.actors.toString()
}
override fun getItemCount(): Int {
return allMovies.size
}
}
MovieListFragment.kt
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MovieListFragment : Fragment() {
private var layoutManager: RecyclerView.LayoutManager? = null
private var adapter: RecyclerView.Adapter<MovieListViewAdapter.ViewHolder>? = null
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: View = inflater.inflate(R.layout.fragment_movie_list, container, false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Toast.makeText(context, "You are viewing the movie list!", Toast.LENGTH_SHORT).show()
}
}
Movies.kt
data class Movies(
val id: Int,
val name: String,
val description: String,
val producer: String,
val actors: Actors
) {
}
Actors.kt
data class Actors(
val name: String
){}
fragment_movie_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MovieListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/movieListId"
android:layout_width="389dp"
android:layout_height="589dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.246" />
</androidx.constraintlayout.widget.ConstraintLayout>
recycler_view_row.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/indexId"
android:layout_width="68dp"
android:layout_height="23dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.045"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.079" />
<TextView
android:id="#+id/nameId"
android:layout_width="68dp"
android:layout_height="21dp"
android:layout_marginBottom="620dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.046"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="#+id/descriptionId"
android:layout_width="68dp"
android:layout_height="19dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/nameId" />
<TextView
android:id="#+id/producerId"
android:layout_width="68dp"
android:layout_height="23dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/descriptionId" />
<TextView
android:id="#+id/actorsId"
android:layout_width="65dp"
android:layout_height="21dp"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/producerId" />
</androidx.constraintlayout.widget.ConstraintLayout>
From the code, I see that the adapter gets instantiated, but never attached to the recyclerView.
You are just missing the following line in the onViewCreated override in the fragment.
findViewById<RecyclerView>(R.id.movieListId).adapter = adapter
https://developer.android.com/guide/topics/ui/layout/recyclerview

Android Jetpack Safe Args Issue

I have an app which has one main activity and 3 fragments. Let's name them A, B and C as shown below:
Fragment A has a RecyclerView. When i click on an item in the
RecyclerView, it passes an Account object in safe args to the
Fragment B where it displays the data.
In Fragment B, the user can then press the edit button to edit the Account object, which then
navigates him/her to Fragment C.
In fragment C, the user can either press the back button to cancel
the modification and return to Fragment B, or the user can modify and
press save to go back to Fragment A.
The problem here is that, if after pressing the edit button, the user perform some modifications and then press the back button without saving, it still modifies the object temporarily (temporarily because if i close the app and then open it again, the object resets to its original state.).
Below is my code (this is a simple code just for the sake of reproducing the issue):
Fragment A.kt
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/Layout_Fragment_Account"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/RecyclerView_Account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{ViewModel.loadingStatus==List.LIST ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment A.kt
private fun subscribeAccounts(accounts: List<Account>) {
val adapter = AccountAdapter(
/* The click listener to handle account on clicks */
AccountClickListener {
navigateTo(AccountFragmentDirections.actionFragmentAccountToFragmentViewAccount(it))
},
/* The click listener to handle popup menu for each accounts */
AccountOptionsClickListener { view, Account ->
//View Popup
}
)
binding.RecyclerViewAccount.apply {
/*
* State that layout size will not change for better performance
*/
setHasFixedSize(true)
/* Bind the layout manager */
layoutManager = LinearLayoutManager(requireContext())
/* Bind the adapter */
this.adapter = adapter
}
/* Submits the list for displaying */
adapter.submitList(accounts)
}
Fragment B.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="Account"
type="...Account" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/Layout_Credential_View"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/Account_Logo"
errorResource="#{#drawable/ic_account_placeholder}"
imageUrl="#{Account.logoUrl}"
loadingResource="#{#drawable/ic_image_loading}"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#drawable/ic_account_placeholder" />
<TextView
android:id="#+id/Account_Name"
style="#style/Locky.Text.Title5.Name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:text="#{Account.entryName}"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/Account_Logo"
tools:text="This can be a very very very long title toooooo" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Fragment B.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
/* Binds the UI */
_binding = FragmentViewAccountBinding.inflate(inflater, container, false)
/* Instantiate the view model */
_viewModel = ViewModelProvider(this).get(ViewAccountViewModel::class.java)
/* Bind lifecycle owner to this */
binding.lifecycleOwner = this
/*
* Fetch the account object from argument
* Then bind account object to layout
*/
val account = ViewAccountFragmentArgs.fromBundle(requireArguments()).accountToVIEW
binding.account = account
_account = account
/* Returns the root view */
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_credentials_actions, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem) =
when (item.itemId) {
R.id.Action_Duplicate -> {
navigateTo(
ViewAccountFragmentDirections.actionFragmentViewAccountToFragmentAddAccount(
_account.apply {
this.id = 0
})
)
true
}
R.id.Action_Edit -> {
navigateTo(
ViewAccountFragmentDirections.actionFragmentViewAccountToFragmentAddAccount(
_account
)
)
true
}
else -> false
}
}
Fragment C.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="...Constants" />
<variable
name="ViewModel"
type="...AddAccountViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp">
...
<!--
**************** Require Text Fields ****************
-->
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/Account_Name"
style="#style/Locky.TextBox.Default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="#string/field_account_name"
app:endIconMode="clear_text"
app:helperText="#string/label_required"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/Barrier_Logo"
app:startIconDrawable="#drawable/ic_profile">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords"
android:text="#={ViewModel.entryName}" />
</com.google.android.material.textfield.TextInputLayout>
...
</LinearLayout>
</layout>
Fragment C.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAddAccountBinding.inflate(inflater, container, false)
/* Binds the UI */
_binding = FragmentAddAccountBinding.inflate(inflater, container, false)
/* Instantiate the view model */
_viewModel = ViewModelProvider(this).get(AddAccountViewModel::class.java)
/* Bind view model to layout */
binding.viewModel = viewModel
/* Bind lifecycle owner to this */
binding.lifecycleOwner = this
/*
* Fetch the account object from argument
* And set it to view model for two way binding
*/
val account = AddAccountFragmentArgs.fromBundle(requireArguments()).accountToADD
viewModel.setAccount(account)
/* Returns the root view */
return binding.root
}
Fragment C view model
class AddAccountViewModel(application: Application) : ObservableViewModel(application) {
/**
* Bindable two-way binding
**/
private lateinit var _account: Account
var entryName: String
#Bindable get() {
return _account.entryName
}
set(value) {
_account.entryName = value
notifyPropertyChanged(BR.entryName)
}
var logoUrl: String?
#Bindable get() {
return _account.logoUrl
}
set(value) {
_account.logoUrl = value ?: ""
notifyPropertyChanged(BR.logoUrl)
}
internal fun setAccount(account: Account?) {
this._account = account ?: Account()
}
}
navigation.xml
<fragment
android:id="#+id/Fragment_A"
android:name="...AccountFragment"
android:label="Accounts"
tools:layout="#layout/fragment_account">
<action
android:id="#+id/action_Fragment_Account_to_BottomSheet_Fragment_Account_Filter"
app:destination="#id/BottomSheet_Fragment_Account_Filter" />
<action
android:id="#+id/action_Fragment_Account_to_Fragment_View_Account"
app:destination="#id/Fragment_View_Account" />
</fragment>
<fragment
android:id="#+id/Fragment_B"
android:name="...ViewAccountFragment"
android:label="View Account"
tools:layout="#layout/fragment_view_account">
<action
android:id="#+id/action_Fragment_View_Account_to_Fragment_Add_Account"
app:destination="#id/Fragment_Add_Account" />
<argument
android:name="ACCOUNT_toVIEW"
app:argType="....Account" />
</fragment>
<fragment
android:id="#+id/Fragment_C"
android:name="...AddAccountFragment"
android:label="Add Account"
tools:layout="#layout/fragment_add_account">
<action
android:id="#+id/action_Fragment_Add_Account_to_BottomSheet_Fragment_Account_Logo"
app:destination="#id/BottomSheet_Fragment_Account_Logo" />
<argument
android:name="ACCOUNT_ToADD"
app:argType="...Account" />
</fragment>
Below is a demonstration of the issue:
I've tried a quick hack where i save an instance of an unmodified account object and before the user leaves the fragment i reset the changes by assigning each variable but this is not efficient. I think i am doing something wrong here with the safe args?
Can someone please help me. I really can't figure this one out. Thank you

How to fix Unresolved reference: setupWithViewPager

I am trying to implement TableLayout on ViewPager but i can not use method setupWithViewPager because android studio says that it is unresolved reference.
This TableLayout and ViewPager should be implemented in fragment (AboutFragment.kt)
My ViewPager works perfectly fine without TabLayout, I mean i can swipe between fragments.
Does somebody have solution?
Here is my AboutFragment:
package hr.ferit.brunozoric.taskie.ui.fragments
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import hr.ferit.brunozoric.taskie.R
import hr.ferit.brunozoric.taskie.ui.adapters.ViewPagerAdapter
import hr.ferit.brunozoric.taskie.ui.fragments.base.BaseFragment
import kotlinx.android.synthetic.main.fragment_about.*
class AboutFragment: BaseFragment() {
override fun getLayoutResourceId(): Int {
return R.layout.fragment_about
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupPager()
}
private fun setupPager() {
viewPager.adapter = ViewPagerAdapter(fragmentManager!!)
tabLayout.setupWithViewPager(viewPager) // I have error in this line :(
}
companion object{
fun newIstance(): Fragment {
return AboutFragment()
}
}
}
Here is my ViewPagerAdapter:
package hr.ferit.brunozoric.taskie.ui.adapters
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import hr.ferit.brunozoric.taskie.ui.fragments.AboutAplicationFragment
class ViewPagerAdapter(manager :FragmentManager) : FragmentStatePagerAdapter(manager) {
companion object{
const val NUM_OF_PAGES =2
const val PAGE_ONE_TITLE = "About Application"
const val PAGE_TWO_TITLE = "About Author"
}
private val frags = mutableListOf<Fragment>(AboutAplicationFragment(),AboutAplicationFragment())
private val titles = mutableListOf(PAGE_ONE_TITLE, PAGE_TWO_TITLE)
override fun getItem(position: Int): Fragment {
return frags[position]
}
override fun getPageTitle(position :Int) : String{
return titles[position]
}
override fun getCount(): Int {
return NUM_OF_PAGES
}
}
Here is XML file with ViewPager and TabLayout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.viewpager.widget.ViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/tabLayout"
/>
<TableLayout
android:id="#+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
/>
</RelativeLayout>
You have used <TableLayout> instead of <TabLayout>.