When I use view binding and call a view with it
And I handle an event and run the program when I click on the view to test it nothing happens
there is no error
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(R.layout.activity_main)
//Test view binding
binding.btnOneNumber.setOnClickListener {
binding.txtView.text = "1"
}
}
}
this is my build.gradle
android{
buildFeatures {
viewBinding = true
}
}
Your setup of viewbinding in the onCreate method is what's causing the problem see - https://developer.android.com/topic/libraries/view-binding#activities for reference.
It is best practice to declare your binding variable above onCreate as a latinit like this - private lateinit var binding: ActivityMainBinding Then you should set the binding variable inside onCreate to the binding class being inflated - binding = ActivityMainBinding.inflate(layoutInflater) Afterwards get the root view - val view = binding.root - and use that instead to set the content view
setContentView(view) Overall you code should look like this
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
//Test view binding
binding.btnOneNumber.setOnClickListener {
binding.txtView.text = "1"
}
}
}
Your app wasn't responding before because you were setting the wrong view to be your layout, with the code I've shown you're setting the view associated with the binding to be your layout which is why the binding becomes responsive.
when you are using binding
val binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
then you must use its root as content view, so
setContentView(binding.root)
enable databinding feature as below
buildFeatures {
viewBinding true
}
don't incude <layout> </layout> in your xml
access like below
val binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
Related
I have implemented a method that launches the BottomSheetDialog but I am facing this error
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:907)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:95)
at android.app.Dialog.show(Dialog.java:342)
at com.example.moveapplication.adapter.PostAdapter.openBottomSheet(PostAdapter.kt:136)
at com.example.moveapplication.adapter.PostAdapter.onBindViewHolder$lambda-2(PostAdapter.kt:53)
at com.example.moveapplication.adapter.PostAdapter.$r8$lambda$Ya4NMbtCP1ASbfWIkCscOWrPyOw(Unknown Source:0)
at com.example.moveapplication.adapter.PostAdapter$$ExternalSyntheticLambda2.onClick(Unknown Source:4)
at android.view.View.performClick(View.java:7125)
at android.view.View.performClickInternal(View.java:7102)
at android.view.View.access$3500(View.java:801)
at android.view.View$PerformClick.run(View.java:27336)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Here is method called after an item click
private fun openBottomSheet(publisher: String) {
val inflater = mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.bottom_sheet_dialog, null)
val uImage = view.findViewById<ImageView>(R.id.sheetIvProfileImage)
val uNumber = view.findViewById<TextView>(R.id.sheetTvOwnerNumber)
val uEmail = view.findViewById<TextView>(R.id.tvOwnerEmail)
val uAbout = view.findViewById<TextView>(R.id.tvOwnerAbout)
val uTitle = view.findViewById<TextView>(R.id.sheetTvOwnerTitle)
val ownersRef = FirebaseFirestore.getInstance().collection("owners").document(publisher)
ownersRef.get().addOnSuccessListener { document ->
val ownerModel = document.toObject(Owner::class.java)
Glide.with(mContext)
.load(ownerModel!!.profileImage)
.into(uImage)
uTitle.text = ownerModel.username
uNumber.text = ownerModel.phoneNumber
uEmail.text = ownerModel.email
uAbout.text = ownerModel.about
}
val bottomSheetDialog = Dialog(mContext)
bottomSheetDialog.apply {
setContentView(view)
setCancelable(true)
window?.setLayout(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
window?.setGravity(Gravity.BOTTOM)
show()
}
}
I am not familiar with another way of doing inflating the bottomsheetdialog. Using the method was a trial to see if it could work.
on my onBindViewHolder the item responsible for click i've implemented it this way
holder.publisher.setOnClickListener {
openBottomSheet(post.publisher)
}
Create a interface
interface RecyclerClickListeners {
fun onRecyclerViewItemClick(view: View, model:Model) }
Add interface to RecyclerAdapter and Fragment
Fragment:
class TestFragment :
Fragment(),
RecyclerClickListeners {
private val recyclerAdapter = TestRecyclerAdapter(arrayListOf(),this)... }
Recycler Adapter:
class TestRecyclerAdapter(private val modelList: ArrayList<Model>, val listener: RecyclerClickListeners) ...
Send your model in onBindViewHolder to Fragment
holder.binding.root.setOnClickListener {
listener.onRecyclerViewItemClick(it, modelList[position])
}
In Fragment call or create bottomsheetdialog
override fun onRecyclerViewItemClick(view: View, model: Model) { openBottonSheetFragment(model) }
Now you can work with ownerModel in Bottom Sheet Fragment
private void showBottomSheetDialog() {
final BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this);
bottomSheetDialog.setContentView(R.layout.bottom_sheet_dialog);
...
bottomSheetDialog.show();
}
call above function on recycler item click
To do that you can easily use material design bottom sheet. first you need to add material dependency to your build.gradle:
dependencies {
// ...
implementation 'com.google.android.material:material:<version>'
// ...
}
then you should create a kotlin file/class and extend BottomSheetDialogFragment()
class ModalBottomSheet : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.modal_bottom_sheet_content, container, false)
}
you can also set different styles to your custom bottomsheet
you can see the basic usage below:
class MainActivity : AppCompatActivity() {
...
val modalBottomSheet = ModalBottomSheet()
modalBottomSheet.show(supportFragmentManager, TAG)
...
}
Update
you can create an interface and use it in your adapter constructor to return the callback to the parent(where you create that adapter)
interface PostAdapterCallback{
onPublisherClicked(publisher : String)
//or
onOwnerClicked(owner: Owner)
}
class PostAdapter(private val publisherList: List<String>, val callback:PostAdapterCallback){}
now you can take your model inside adapter and pass the Owner model as callback or you can pass your publisher to your Activity and call your FirebaseFirestore.getInstance() there.
in your activity:
override fun onOwnerClicked(owner : Owner){
//you can call you bottomsheet here and pass you model as a bundle if you need
}
I'm looking for a list of movies in an API, the project is in MVVM and the search is being done perfectly. However, when opening the app, the user has to exit and open it again for the list to display the results. How to solve this?
In onStart I use Observe
override fun onStart() {
super.onStart()
Log.d("TESTE", "onStart")
viewModel.movieList.observe(this, Observer {
Log.d("TAG", "onCreate: $it")
for (i in it.results){
list.add(i)
}
if (page < 66){
page ++
}
})
In onResume
override fun onResume() {
super.onResume()
val scope = MainScope()
adapter.setDataSet(list)
scope.launch {
viewModel.getAllMovies(page)
viewModel.getAllGenre()
}
val recyclerView : RecyclerView = findViewById(R.id.recycler_vie_movie_list)
val layoutManager = GridLayoutManager(applicationContext, 3, GridLayoutManager.VERTICAL, false)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
You are using activity callbacks incorrectly. White all of these code in onCreate (if you use Activity or use onViewCreated if it is Fragment).
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.getMovieList() // realize this method in your ViewModel
// and don't forget to set data to your movieList LiveData
val recyclerView : RecyclerView = findViewById(R.id.recycler_vie_movie_list)
val layoutManager = GridLayoutManager(applicationContext, 3, GridLayoutManager.VERTICAL, false)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
viewModel.movieList.observe(this, Observer { list->
adapter.setDataSet(list)
})
Inside the method setDataSet call notifyDataSetChanged()
Good luck in your endeavors!
I have a Fragment_A on Activity_1 and whenever a Button is clicked in the Fragment_A I have to start Activity_2 witch have a Fragment_B
So i need to send a user_Id from the Fragment_A in Activity 1 to the Fragment_B in activity_2.
Is there any way to send the data directly from Frament_A in Activity_1 to Fragment_B in Activity_2?
I am a student , so i am a beginner and i did found this question here but i didn't understand what to do to solve this
I did find some solution about interface but i didn't know how to do it
i keep getting class_id null
Language : Kotlin
Class : ClassesAdapter
class ClassesAdapter(val c:Fragment,val l:android.content.Context? ,val classeslist: ArrayList<Classes>) : RecyclerView.Adapter <ClassesAdapter.ClassesViewHolder> (){
lateinit var auth: FirebaseAuth
lateinit var DataBase : DatabaseReference
lateinit var DataBase2 : DatabaseReference
lateinit var clipboardManager: ClipboardManager
private lateinit var mListener :onItemClickListener
interface onItemClickListener {
fun onItemClick(position :Int)
}
/*fun setOnItemClickListener(listener : onItemClickListener){
// mListener=listener
}*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ClassesViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.class_items2,parent,false)
//return ClassesViewHolder(itemView,mListener)
return ClassesViewHolder(itemView,)
}
override fun onBindViewHolder(holder: ClassesViewHolder, position: Int) {
val currentitem = classeslist[position]
holder.classname.text = currentitem.name
holder.classrole.text = currentitem.role
holder.itemView.setOnClickListener {
val intent = Intent(l, DetailedClassActivity::class.java)
intent.putExtra("classId", currentitem.class_id)
l!!.startActivity(intent)
}
Class : Acttivity2 who has the fragment iwant to send it the class_id`
package com.example.myclass1
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.viewpager2.widget.ViewPager2
import com.example.myclass1.databinding.ActivityDetailedClassBinding
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.google.firebase.auth.FirebaseAuth
class DetailedClassActivity : DrawerBaseActivity() {
private lateinit var binding: ActivityDetailedClassBinding
//lateinit var toggle: ActionBarDrawerToggle
override lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityDetailedClassBinding.inflate(layoutInflater)
setContentView(binding.root)
// add it to change the toolbar title's to the activity name
val actionBar = supportActionBar
if (actionBar != null) {
val dynamicTitle: String = HomeActivity::class.java.simpleName
//Setting a dynamic title at runtime. Here, it displays the current activity name
actionBar.setTitle("Home")
}
// navigate between documents ,notification and chatroom : tabLayout
val tablayout2 : TabLayout =findViewById(R.id.tab_layout2)
val viewpager21 : ViewPager2 =findViewById(R.id.viewPager21)
val adapter =PagerAdapter2(supportFragmentManager,lifecycle)
viewpager21.adapter = adapter
TabLayoutMediator(tablayout2, viewpager21) { tab, position ->
when(position){
0->{tab.text="Documents"}
1->{tab.text="Note"}
2->{tab.text="ChatRoom"}
}
}.attach()
val intent=getIntent()
var classId= intent.getStringExtra("classId")
fun getMyData(): String? {
return classId
}
}
class : fragment B
enter code here
btnAddcourse.setOnClickListener {
var nameofthecourse = Coursename.text.toString().trim()
var course_id :String?=DataBase3.push().key
var classId = arguments?.getString("classId")
val dateofthecourse: String = formatTimeStamp()
if (TextUtils.isEmpty(nameofthecourse)) {
// No name added
Coursename.error = "No name added !! Please enter the name of the class"
}
else{
courseList.add(Course(course_id,nameofthecourse,dateofthecourse,classId))
coursesAdapter.notifyDataSetChanged()
Toast.makeText(activity,"Adding Course Success", Toast.LENGTH_SHORT).show()
//Toast.makeText(activity,"class_id is null ", Toast.LENGTH_SHORT).show()
if (classId != null) {
addCourseToDataBase(course_id!!,nameofthecourse,dateofthecourse,classId)
}else{
Toast.makeText(activity,"class_id is null ", Toast.LENGTH_SHORT).show()
}
}
}
fragmentA
button.setOnClickListener {
val intent = Intent(requireContext(), Activity2::class.java)
intent.putExtra("user_Id", user_Id)
startActivity(intent)
}
activity2
val intent = getIntent()
val message = intent.getStringExtra("user_Id")
val bundle = Bundle()
bundle.putString("user_Id", message) //the part i updated
val fragmet= fragment_B()
fragmet.setArguments(bundle)
fragmentB
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_B, container, false)
val bundle = this.arguments //the part i updated
val myString = bundle!!.getString("user_Id", "defaultValue") //the part i updated
}
1-Read About Singleton Design Pattern
for make user id global in all app
read it to understand
2-Shared Preference :save user id to can using for all app
Shared Preferences
3-you can try Navigation Args : better choose for performance
and can use it for movement through you app
Navigation
Hello fellow programmers!
I am currently facing problem with RecyclerView's add animation with smooth scroll simulataneously.
Problem
I have implemented RecyclerView with ListAdapter and DiffUtil for cool animations (insert, remove). The problem is that I can't get it to smooth scroll to added position without cancelling insert animation.
User is supposed to type in 'exercise name' and then push 'add' button to add it to current 'exercise list'. Then this exercise should be added to RecyclerView list. Everything works fine here, but I can't achieve smooth scroll to new item position without cancelling (or accelerating) insert animation.
viewModel.exerciseListLiveData.observe(viewLifecycleOwner, {
adapter.submitList(it) {
// Cancels insert animation (or accelerates it rapidly) - bad UX...
layoutManager.smoothScrollToPosition(recyclerView, RecyclerView.State(), 0)
}
})
On the other hand, substituting smoothScrollToPosition with scrollToPosition works fine but when there's more items (recycler's view is filled from top to bottom) it blinks on scroll. I know it's rather jump than smooth scroll but then, why is it working with add animation so it doesn't cancel or accelerate?
viewModel.exerciseListLiveData.observe(viewLifecycleOwner, {
adapter.submitList(it) {
// Works fine (recyclerView is scrolling to added position)
// but when there's no room to display more items - it scrolls to it with blink...
// which I simply can't stand!
layoutManager.smoothScrollToPosition(recyclerView, RecyclerView.State(), 0)
}
})
Possible solution?
I'm supposed to first smoothScrollToPosition(0) to top-most position, then call submitList(it) and finally call scrollToPosition(0) but I can't achieve that (it is doing so fast, simultaneously, that it's layering each other). Maybe I should use coroutines? Don't know...
How am I supposed to add proper delay? Maybe I am doing something wrong because i read that some programmers had this autoscroll when added item.
Code sample
Adapter
class ExercisesListAdapter :
ListAdapter<Exercise, ExercisesListAdapter.ExerciseItemViewHolder>(ExercisesListDiffUtil()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExerciseItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding: ItemRecyclerviewExerciseBinding =
DataBindingUtil.inflate(inflater, R.layout.item_recyclerview_exercise, parent, false)
return ExerciseItemViewHolder(binding)
}
override fun onBindViewHolder(holder: ExerciseItemViewHolder, position: Int) {
val currentExercise = getItem(position)
holder.bind(currentExercise)
}
override fun getItemId(position: Int): Long {
return getItem(position).id
}
// Item ViewHolder class
class ExerciseItemViewHolder(private val binding: ItemRecyclerviewExerciseBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(itemExercise: Exercise) {
binding.textCardExerciseName.text = itemExercise.name
}
}
}
DiffUtil
class ExercisesListDiffUtil: DiffUtil.ItemCallback<Exercise>() {
override fun areItemsTheSame(oldItem: Exercise, newItem: Exercise): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Exercise, newItem: Exercise): Boolean {
return oldItem == newItem
}
}
ViewModel
class CreateWorkoutViewModel(private val repository: WorkoutRepository) : ViewModel() {
private var exerciseId = 0L
private var exerciseList = mutableListOf<Exercise>()
val exerciseName = MutableLiveData<String?>()
private val _exerciseListLiveData = MutableLiveData<List<Exercise>>()
val exerciseListLiveData: LiveData<List<Exercise>>
get() = _exerciseListLiveData
fun onAddExercise() {
if (canValidateExerciseName())
addExerciseToList()
}
fun onAddWorkout() {
exerciseList.removeAt(2)
_exerciseListLiveData.value = exerciseList.toList()
}
private fun canValidateExerciseName(): Boolean {
return !exerciseName.value.isNullOrBlank()
}
private fun addExerciseToList() {
exerciseList.add(0, Exercise(exerciseId++, exerciseName.value!!))
_exerciseListLiveData.value = exerciseList.toList()
}
}
Fragment
class CreateWorkoutFragment : Fragment() {
lateinit var binding: FragmentCreateWorkoutBinding
lateinit var viewModel: CreateWorkoutViewModel
lateinit var recyclerView: RecyclerView
lateinit var adapter: ExercisesListAdapter
lateinit var layoutManager: LinearLayoutManager
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_create_workout, container, false)
setViewModel()
setRecyclerView()
// Observers:
// Update RecyclerView list on change
viewModel.exerciseListLiveData.observe(viewLifecycleOwner, {
adapter.submitList(it) {
layoutManager.smoothScrollToPosition(recyclerView, RecyclerView.State(), 0)
}
})
return binding.root
}
private fun setRecyclerView() {
recyclerView = binding.recyclerviewExercises
setLayoutManager()
setAdapter()
}
private fun setLayoutManager() {
layoutManager = LinearLayoutManager(context)
recyclerView.layoutManager = layoutManager
}
private fun setAdapter() {
adapter = ExercisesListAdapter()
adapter.setHasStableIds(true)
recyclerView.adapter = adapter
}
private fun setViewModel() {
val dao = WorkoutDatabase.getInstance(requireContext()).workoutDAO
val repository = WorkoutRepository(dao)
val factory = CreateWorkoutViewModelFactory(repository)
viewModel = ViewModelProvider(this, factory).get(CreateWorkoutViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
}
EDIT
I got it working but it's not 'elegant' way and insert animation is canceled through process :/
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if(layoutManager.findFirstCompletelyVisibleItemPosition() == 0)
layoutManager.scrollToPosition(0)
else {
recyclerView.clearAnimation()
layoutManager.smoothScrollToPosition(recyclerView, RecyclerView.State(), 0)
}
}
})
I have A Bottom Navigation Bar Using Navigation Components It Contain 3 Fragments let Say A(Home), B And C. When I Moved From B To C Fragment B Destroys And Similarly C To B C Destroys. When I Re-Select A It Again Makes Network Call Fetches The Data and Display. I Don't Want To Make A Network Request Again And Again When Fragment Destroys Or Re-Selected.
I'm Using ViewModel, Retrofit, LiveData, Coroutines And Kotlin.
Please Help Me To Fix This Problem
Base Fragment
abstract class BaseFragment<VM: ViewModel, VB: ViewBinding, R: BaseRepository>:Fragment() {
private var _viewBinding: VB? = null
protected val viewBinding
get() = _viewBinding!!
protected lateinit var viewModel: VM
protected lateinit var userTokenData: UserTokenData
protected lateinit var remoteDataStore: RemoteDataSource
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
remoteDataStore = RemoteDataSource()
userTokenData = UserTokenData(requireContext())
_viewBinding = getFragmentBinding(inflater, container)
val factory = ViewModelFactory(getRepository())
viewModel = ViewModelProvider(this, factory).get(getViewModelClass())
lifecycleScope.launch {
userTokenData.token.first()
}
return viewBinding.root
}
abstract fun getViewModelClass():Class<VM>
abstract fun getFragmentBinding(inflater: LayoutInflater, container: ViewGroup?): VB
abstract fun getRepository(): R
override fun onDestroyView() {
super.onDestroyView()
Log.d(MyTAG, "onDestroyView")
_viewBinding = null
}
Home Fragment
class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding, UserRepository>() {
override fun getViewModelClass() = HomeViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentHomeBinding.inflate(inflater, container, false)
override fun getRepository(): UserRepository {
val token = runBlocking { userTokenData.token.first() }
return UserRepository(remoteDataStore.buildApi(UserApi::class.java, token))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewBinding.progressBar.visible(true)
viewModel.user.observe(viewLifecycleOwner, Observer {
when(it){
is Resources.Success -> {
updateUI(it.value.user)
}
is Resources.Loading -> {
viewBinding.progressBar.visible(true)
}
}
})
}
fun updateUI(user: User){
viewBinding.progressBar.visible(false)
viewBinding.email.text = user.email
viewBinding.name.text = user.name
}
}
In each Fragment, put this before onCreate
private val myViewModel: MyViewModel by activityViewModels()
Also, in order to do that, you will need to have this dependency:
implementation "androidx.fragment:fragment-ktx:1.2.5"