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!
Related
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)
i am new in android, i am working on an app that retrieve data from server using retrofit and kodein and MvvM in kotlin
i set a Navigation drawer in my app and the purpose is that when i click on the item of navigation drawer
new activity opens and in this activity i want show recyclerview
but when new activty opens recyclerview cant set listitem on recycler
i debug my code in my repositories and viewmodel class and i see that they received data
i debug my code in new activty and i see that viewmodel cant receive those data and set a invalid icon next to my code in viewmodel.observe
this is my repository class:
fun getdigitalproduct(): LiveData<List<DigitalProduct>>{
val dpData:MutableLiveData<List<DigitalProduct>> = MutableLiveData<List<DigitalProduct>>()
val apiClient = ApiClient()
val call:Call<List<DigitalProduct>> = apiClient.getClient().create(ApiService::class.java).getdigitalproduct()
call.enqueue(object : Callback<List<DigitalProduct>>{
override fun onResponse(
call: Call<List<DigitalProduct>>,
response: Response<List<DigitalProduct>>
) {
dpData.value = response.body()
}
override fun onFailure(call: Call<List<DigitalProduct>>, t: Throwable) {
dpData.value = null
}
})
return dpData
}
this is for ViewModel
var repoDigitalProduct: LiveData<List<DigitalProduct>> = repositorys.getdigitalproduct()
fun getdigitalproduct(): LiveData<List<DigitalProduct>>{
return repoDigitalProduct
}
this is for new activity:
private fun getDigitalProduct() {
viewModel.getdigitalproduct().observe(this, Observer {
digipro.addAll(it)
})
this is digipro:
var digipro: ArrayList<DigitalProduct> = ArrayList()
and this i use this code in oncreate method in new activity:
viewModel = ViewModelProviders.of(this, factory).get(AllViewModel::class.java)
getDigitalProduct()
setdigitalProductRecycler()
i use viewmodelprovider.of code in Mainactivity too
this is for setdigitalProductRecycler:
private fun setdigitalProductRecycler() {
val digiproRecycler = digital_product_recycler
digiproRecycler.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true)
digiproRecycler.adapter = DigitalProductAdapter(digipro)
}
my codes works in Mainactivity but when i try it in new activity.........
what should i do?
The best way to solve this issue is create your viewmodel in activity or you can use shared viewmodel. So your viewmodel will retain as your activity retain
https://stackoverflow.com/a/52611554/8868638
i put repositorie and viewmodel in companion object then i called viewmodel of MainActivity in that activity
this is for MainActivty:
companion object{
val repositorys = AllRepositorys()
var viewModel: AllViewModel = AllViewModel(repositorys)
}
this is for newActivity:
lateinit var viewModel: AllViewModel
and i put this in oncreate() Method in new activity:
viewModel = MainActivity.viewModel
UPDATE
Now I handle this issue with creating a viewmodel for every fragment or activity. Because, ViewModel is designed to store and manage UI-related data in a lifecycle of Activity or Fragment
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 am new to Firebase and RecyclerViews and I am trying to get a list of ingredients from my Firebase Database and I want to create checkboxes for every item I retrieve from the database. I got as far as creating a RecyclerView and adapter for my Firebase Query but binding lists from the query doesn't seem to be covered anywhere I could find.
Please find below the code for putting the data into the recycler view as well as the layout files being used for the recycler view.
Data being retrieved
that I want to display the original field of my object as checkboxes
ShoppingListFragment.kt
class ShoppingListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Enable Firestore logging
FirebaseFirestore.setLoggingEnabled(true)
//Firestore
firestore = Firebase.firestore
// Get recipes that the user has liked up to ${LIMIT}
query = firestore.collection("recipes").whereArrayContains(
"plannedBy",
Firebase.auth.uid.toString()
)
.orderBy("name", Query.Direction.ASCENDING)
.limit(LIMIT.toLong())
// Init the adapter to hold the recipe objects.
adapter = object : ShoppingListAdapter(query) {
override fun onDataChanged() {
if (itemCount == 0) {
shoppingListRecycler.visibility = View.GONE
viewEmpty.visibility = View.VISIBLE
} else {
shoppingListRecycler.visibility = View.VISIBLE
viewEmpty.visibility = View.GONE
}
}
override fun onError(e: FirebaseFirestoreException) {
// Show a snackbar on errors
view?.let {
Snackbar.make(
it,
"Error: check logs for info.", Snackbar.LENGTH_LONG
).show()
}
}
}
...
}
...
}
ShoppingListAdapter.kt
open class ShoppingListAdapter(query: Query) :
FirestoreAdapter<ShoppingListViewHolder>(query) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShoppingListViewHolder {
return ShoppingListViewHolder(
ItemShoppingListBinding.inflate(
LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ShoppingListViewHolder, position: Int) {
holder.bind(getSnapshot(position))
}
}
ShoppingListViewHolder.kt
class ShoppingListViewHolder(val binding: ItemShoppingListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(snapshot: DocumentSnapshot) {
val recipe = snapshot.toObject<Recipe>() ?: return
binding.listTitle.text = recipe.name
if(recipe.ingredients.size > 1) {
binding.shoppingListItem.text = recipe.ingredients[0].original
for(i in 1 until recipe.ingredients.size) {
val checkBox = CheckBox() //Not sure what to put here as the context for my recyclerview List Item
checkBox.text = recipe.ingredients[i].original
checkBox.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
binding.shoppingListContainer.addView(checkBox)
}
} else {
binding.shoppingListItem.text = recipe.ingredients[0].original
}
}
}
I have tried passing a context parameter through ShoppingListViewHolder.kt and in turn adding that parameter to the adapter and so forth like so.
class ShoppingListViewHolder(val binding: ItemShoppingListBinding, val context: Context) : RecyclerView.ViewHolder(binding.root) {
fun bind(snapshot: DocumentSnapshot) {
val recipe = snapshot.toObject<Recipe>() ?: return
binding.listTitle.text = recipe.name
if(recipe.ingredients.size > 1) {
binding.shoppingListItem.text = recipe.ingredients[0].original
for(i in 1 until recipe.ingredients.size) {
val checkBox = CheckBox(context)
...
}
ShoppingListFragment.kt
// Init the adapter to hold the recipe objects.
adapter = object : ShoppingListAdapter(query, requireContext()) {
override fun onDataChanged() {
if (itemCount == 0) {
shoppingListRecycler.visibility = View.GONE
viewEmpty.visibility = View.VISIBLE
} else {
shoppingListRecycler.visibility = View.VISIBLE
viewEmpty.visibility = View.GONE
}
}
override fun onError(e: FirebaseFirestoreException) {
// Show a snackbar on errors
view?.let {
Snackbar.make(
it,
"Error: check logs for info.", Snackbar.LENGTH_LONG
).show()
}
}
}
But all that did was create a huge amount of white space between entries. After some debugging, it looks like passing the context from the fragment gave it the MainActivity context. I'm not too sure where to go from here and any help would be appreciated. Thank you!
you can use binding.root.context except passing context
I have an activity that shows two fragments at the same time on tablet and one at a time on handset. Because I am making the app for both tablets and android, I have to separate the functionality of the navigation between fragments into a separate function "displaySecondFragmentOnHandset()".
On the smaller handset when a move from the first fragment to the second and the then try to go back to the first, the screen is blank.
MyActivity:
class CentralActivity : AppCompatActivity() {
val manager = supportFragmentManager
var firstFrag : FirstFrag? = null
override fun onCreate(savedInstanceState: Bundle?) {
...
var ft: FragmentTransaction = manager.beginTransaction();
firstFrag = FirstFrag.newInstance()
ft.add(R.id.real_container, firstFrag!!, "firstFrag")
if (screenLayoutSize >= Configuration.SCREENLAYOUT_SIZE_LARGE) {
var secondFrag = SecondFrag.newInstance()
ft.add(R.id.real_container, secondFrag, "secondFrag")
}
ft.commit()
}
fun displaySecondFragmentOnHandset (){
var secondFrag = SecondFrag.newInstance()
var ft: FragmentTransaction = manager.beginTransaction();
ft!!.replace(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
}
Then in my FirstFragemnt, if i'm on a smaller handset I do:
class FirstFragemnt : Fragment() {
private var viewModel: SharedViewModel? = null
fun goToSecondFragment(){
if (screenLayoutSize!! < Configuration.SCREENLAYOUT_SIZE_LARGE) {
viewModel!!.setMsgInCommunicator(collection)
var centralActivity: CentralActivity = activity as CentralActivity
centralActivity.displaySecondFragmentOnHandset()
}
}
The problem is when I press the back button to go to the first fragment, The screen is blank.
in CentralActivity replace
ft!!.replace(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
with
ft!!.add(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
The solution was to put the fun goToSecondFragment() inside of override fun onViewCreated