I am writing a simple Kotlin app and I want to send data from LessonsModule1Fragment to DetailsFragment and try to pass bundle variables. Depending on the position, the fragment should be inflated with the appropriate layout.
I'm trying to pass a position between fragments, but I always get null
LessonsModule1Fragment
class LessonsModule1Fragment : Fragment(), OnLessonClickListener {
private var layoutManager: RecyclerView.LayoutManager? = null
private var adapter: RecyclerView.Adapter<RecyclerViewLessonAdapter.ViewHolder>? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val binding = inflater.inflate(R.layout.fragment_module1_lessons, container, false)
val recyclerView = binding.findViewById<RecyclerView>(R.id.lesson_recycler_view)
layoutManager = LinearLayoutManager(context)
recyclerView.layoutManager = layoutManager
adapter = RecyclerViewLessonAdapter(this)
recyclerView.adapter = adapter
return binding
}
override fun lessonClick(lesson: Lesson, position: Int) {
var bundle = Bundle()
bundle.putInt("POSITION", position)
var transaction = this.parentFragmentManager.beginTransaction()
var fragment = DetailsFragment()
fragment.arguments = bundle
transaction.replace(R.id.lesson_recycle_layout, fragment).commit()
findNavController().navigate(R.id.action_lessonsModule1Fragment_to_detailsFragment)
}
DetailsFragment
class DetailsFragment : Fragment() {
private var num: Int? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
num = arguments?.getInt("POSITION")
when (num) {
0 -> return inflater.inflate(R.layout.fragment_details, container, false)
1 -> return inflater.inflate(R.layout.fragment_details2, container, false)
2 -> return inflater.inflate(R.layout.fragment_details3, container, false)
}
return inflater.inflate(R.layout.fragment_details, container, false)
}}
RecyclerViewLessonAdapter
class RecyclerViewLessonAdapter (private val lessonClickListener: OnLessonClickListener) : RecyclerView.Adapter<RecyclerViewLessonAdapter.ViewHolder>() {
private val lessons = DataStorage.getLessonsList()
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val itemTitle: TextView = itemView.findViewById(R.id.lesson_title)
val itemDetails: TextView = itemView.findViewById(R.id.lesson_description)
val itemImage: ImageView = itemView.findViewById(R.id.lesson_image)
fun lessonBind(lesson: Lesson, clickListener: OnLessonClickListener) {
itemView.setOnClickListener {
clickListener.lessonClick(lesson, adapterPosition)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.lesson_card, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemImage.setImageResource(lessons[position].images)
holder.itemTitle.text = lessons[position].title
holder.itemDetails.text = lessons[position].details
val lesson = lessons[position]
holder.lessonBind(lesson, lessonClickListener)
}
override fun getItemCount() = lessons.size
}
OnLessonClickListener
interface OnLessonClickListener {
fun lessonClick(lesson: Lesson, position: Int)
}
Perhaps there is a more suitable way?
I used navigation arguments, added these lines and it worked. Thanks for the hint
val action = LessonsModule1FragmentDirections.actionLessonsModule1FragmentToDetailsFragment(position)
findNavController().navigate(action)
Related
Hi I have been trying to seperate my list of washing machines to make them one by one in grid view but for some reason they keep getting stuck together like in the image.[![enter image description here][1]][1]
My Fragment that has most of the work going on in the background.
class HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding
private val viewModel by KoinJavaComponent.inject(HomeFragmentVM::class.java)
private val sp by inject<SPManager>()
private var profile : Data? = null
private val itemList: Array<String>
get() = arrayOf(viewModel.products.toString())
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getWashingMachines()
setObservers()
setupGridView()
}
private fun setupGridView() {
println(viewModel.products)
}
private fun setObservers() {
val strings = arrayOf(viewModel.products)
viewModel.isCorrect.observe(viewLifecycleOwner) {
if (it.isNotEmpty()){
itemList.contentEquals(arrayOf(viewModel.products))
val adapter = ImageListAdapter(context!!, R.layout.list_item, itemList)
binding.gridview.adapter = adapter
binding.gridview.onItemClickListener =
AdapterView.OnItemClickListener { parent, v, position, id ->
println("you clicked")
}
println("Not Failed")
}
else
{
println("Failed")
}
}
}
class HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding
private val viewModel by KoinJavaComponent.inject(HomeFragmentVM::class.java)
private val sp by inject<SPManager>()
private var profile : Data? = null
private val itemList: Array<String>
get() = arrayOf(viewModel.products.toString())
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getWashingMachines()
setObservers()
setupGridView()
}
private fun setupGridView() {
println(viewModel.products)
}
private fun setObservers() {
val strings = arrayOf(viewModel.products)
viewModel.isCorrect.observe(viewLifecycleOwner) {
if (it.isNotEmpty()){
itemList.contentEquals(arrayOf(viewModel.products))
val adapter = ImageListAdapter(context!!, R.layout.list_item, itemList)
binding.gridview.adapter = adapter
binding.gridview.onItemClickListener =
AdapterView.OnItemClickListener { parent, v, position, id ->
println("you clicked")
}
println("Not Failed")
}
else
{
println("Failed")
}
}
}
The Adapter
internal class ImageListAdapter internal constructor(
context: Context,
private val resource: Int,
private val itemList: Array<String>?
) : ArrayAdapter<ImageListAdapter.ItemViewHolder>(context, resource) {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private lateinit var itemBinding: ListItemBinding
override fun getCount(): Int {
return if (this.itemList != null) this.itemList.size else 0
}
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
var convertView = view
val holder: ItemViewHolder
if (convertView == null) {
itemBinding = ListItemBinding.inflate(inflater)
convertView = itemBinding.root
holder = ItemViewHolder()
holder.name = itemBinding.textView
holder.icon = itemBinding.icon
convertView.tag = holder
} else {
holder = convertView.tag as ItemViewHolder
}
holder.name!!.text = this.itemList!![position]
holder.icon!!.setImageResource(R.mipmap.ic_launcher)
return convertView
}
internal class ItemViewHolder {
var name: TextView? = null
var icon: ImageView? = null
}
}
The Api call works its just how do I separate this one by one to make each a grid layout view many thanks
After sawing your code, I think the problem is on
private val itemList: Array<String>
get() = arrayOf(viewModel.products.toString())
Because you are converting your list of products to ONE string when you call 'toString()', and inserting that one string into one list, passing than that one list with one item (the string) to the adapter
You should replace
private val itemList: Array<String>
get() = arrayOf(viewModel.products.toString())
with
private val itemList:Array<String>
get() {
val out = viewModel.products.map { it ->
it.toString()
}
return out
}
I have home fragment and I want it to go to another fragment which will show its "details", which passes the data of the clicked recyclerview item to that "details" fragment.
When I click the article it will move to detail article which passes the data.
As for the code, here's the adapter:
class ArticleAdapter(private val articleList: ArrayList<Article>) :
RecyclerView.Adapter<ArticleAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = ItemArticleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val data = articleList[position]
holder.bind(data)
}
class MyViewHolder(private val binding: ItemArticleBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(data: Article) {
Glide.with(binding.root.context)
.load(data.img)
.into(binding.articleImage)
binding.articleTitle.text = data.title
binding.root.setOnClickListener {
val article = Article(
data.title,
data.img,
data.content
)
}
}
}
override fun getItemCount(): Int {
return articleList.size
}
}
Here's my detailFragment
class DetailArticleFragment : Fragment() {
private var _binding: FragmentDetailArticleBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentDetailArticleBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val intent = Intent(binding.root.context, DetailArticleFragment::class.java)
val article = intent.getParcelableExtra<Article>(DETAIL_ARTICLE) as Article
Glide.with(this)
.load(article.img)
.into(_binding!!.articleImage)
_binding!!.articleTitle.text = article.title
_binding!!.articleDescription.text = article.content
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
const val DETAIL_ARTICLE = "detail_article"
}
}
You need to pass click listener interface to adapter, for example:
typealias OnArticleClick = (article: Article) -> Unit
class ArticleAdapter(
private val articleList: ArrayList<Article>
) :
RecyclerView.Adapter<ArticleAdapter.MyViewHolder>() {
var onArticleClick: OnArticleClick? = null
...
binding.root.setOnClickListener {
val article = Article(
data.title,
data.img,
data.content
)
onArticleClick?.invoke(article)
}
...
}
And init onArticleClick in your home fragment with RecyclerView:
adapter.apply {
onArticleClick = {
// show details fragment
}
}
I made an app from which I get data from TMDB API, everything seems to work but when I start the app, it displays only hint text, after scrolling the View get's updated with the data from the API
This is how it looks before scrolling
And this is how it looks after scrolling down a a bit
This is how I implemented it:
HomeFragment.kt
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private lateinit var popularMovies: RecyclerView
private lateinit var popularMoviesAdapter: MoviesAdapter
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel = ViewModelProvider(this)[HomeViewModel::class.java]
_binding = FragmentHomeBinding.inflate(inflater, container, false)
popularMovies = binding.popularMovies
popularMovies.layoutManager = LinearLayoutManager(context,LinearLayoutManager.VERTICAL,false)
popularMoviesAdapter = MoviesAdapter(listOf())
popularMovies.addItemDecoration(DividerItemDecoration(context,DividerItemDecoration.VERTICAL))
popularMovies.adapter = popularMoviesAdapter
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
MoviesRepository.getPopularMovies(page = 1,onSuccess = ::onPopularMoviesFetched,onError = ::onError)
}
private fun onPopularMoviesFetched(movies: List<Movie>) {
popularMoviesAdapter.updateMovies(movies)
}
private fun onError() {
Toast.makeText(context, getString(R.string.error_fetch_movies), Toast.LENGTH_SHORT).show()
}
MovieAdapter.kt
class MoviesAdapter(
private var movies: List<Movie>
) : RecyclerView.Adapter<MoviesAdapter.MovieViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val view = LayoutInflater
.from(parent.context)
val binding = MovieItemBinding.inflate(view)
return MovieViewHolder(binding)
}
override fun getItemCount(): Int = movies.size
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
holder.bind(movies[position])
}
fun updateMovies(movies: List<Movie>) {
this.movies = movies
notifyDataSetChanged()
}
inner class MovieViewHolder(private val binding: MovieItemBinding) : RecyclerView.ViewHolder(binding.root) {
private val poster: ImageView = itemView.findViewById(R.id.item_movie_poster)
fun bind(movie: Movie) {
binding.movieTitle.text =movie.title
binding.movieReleaseDate.text = movie.releaseDate
binding.movieOverview.text = movie.overview
binding.movieReleaseDate.text = movie.releaseDate
Glide.with(itemView)
.load("https://image.tmdb.org/t/p/w342${movie.posterPath}")
.transform(CenterCrop())
.into(poster)
}
}
make adapter initialization on onViewCreated instead of onCreateView
I am fairly new to android development.
So I tried to add data into recyclerview through another fragment by appending data into existing datalist. I tried to add data to the list on save button click and notify that change to the adapter .But it doesn't work need help.
dataSource.kt
class dataSource {
var dataList = mutableListOf<RVData>(
RVData("Jhon","An Accountant"),
RVData("Alex","Doctor"),
RVData("Jhon","An Accountant"),
RVData("Alex","Doctor"),
RVData("Jhon","An Accountant"),
RVData("Alex","Doctor"),
)
fun loadData():List<RVData>{
return dataList
}
}
FirstFragment.kt
class FirstFragment : Fragment() {
val data = dataSource().loadData()
val adapter = RVAdapter(data)
lateinit var navController:NavController
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val rView = view.findViewById<RecyclerView>(R.id.recyclerView)
rView.adapter = adapter
rView.layoutManager = LinearLayoutManager(activity)
val addBtn = view.findViewById<Button>(R.id.materialButton)
addBtn.setOnClickListener{
toNextScreen(view)
}
}
fun toNextScreen(view:View){
navController = Navigation.findNavController(view)
navController.navigate(R.id.action_firstFragment_to_secondFragment)
}
}class FirstFragment : Fragment() {
val data = dataSource().loadData()
val adapter = RVAdapter(data)
lateinit var navController:NavController
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val rView = view.findViewById<RecyclerView>(R.id.recyclerView)
rView.adapter = adapter
rView.layoutManager = LinearLayoutManager(activity)
val addBtn = view.findViewById<Button>(R.id.materialButton)
addBtn.setOnClickListener{
toNextScreen(view)
}
}
fun toNextScreen(view:View){
navController = Navigation.findNavController(view)
navController.navigate(R.id.action_firstFragment_to_secondFragment)
}
}
RVAdapter.kt
class RVAdapter (
var listItems:List<RVData>
):RecyclerView.Adapter<RVAdapter.RecyclerViewHolder>(){
inner class RecyclerViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val itemName = itemView.findViewById<TextView>(R.id.tvName)
val itemDesc = itemView.findViewById<TextView>(R.id.tvDesc)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recycler_view_item,parent,false)
return RecyclerViewHolder(view)
}
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
val dataItem = listItems[position]
holder.apply{
itemName.text = dataItem.name
itemDesc.text = dataItem.description
}
}
override fun getItemCount(): Int {
return listItems.size
}
}
SecondFragment.kt
class SecondFragment : Fragment() {
lateinit var navController: NavController
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_second, container, false)
val name = view.findViewById<EditText>(R.id.editText1)
val desc = view.findViewById<EditText>(R.id.editText2)
val btn = view.findViewById<Button>(R.id.materialButton2)
btn.setOnClickListener{
dataSource().dataList.add(
RVData(name.text.toString(),desc.text.toString())
)
FirstFragment().adapter.notifyDataSetChanged()
toNextScreen(view)
}
return view
}
fun toNextScreen(view:View){
navController = Navigation.findNavController(view)
navController.navigate(R.id.action_secondFragment_to_firstFragment)
}
}
put the dataList in dataSource model inside companion object
class dataSource {
companion object{
var dataList = mutableListOf<RVData>(
RVData("Jhon","An Accountant"),
RVData("Alex","Doctor"),
RVData("Jhon","An Accountant"),
RVData("Alex","Doctor"),
RVData("Jhon","An Accountant"),
RVData("Alex","Doctor"),
)
}
fun loadData():List<RVData>{
return dataList
}
}
In SecondFragment
dataSource.dataList.add(
RVData(name.text.toString(),desc.text.toString())
)
Remove this line from second fragment
FirstFragment().adapter.notifyDataSetChanged()
btn.setOnClickListener{
dataSource().dataList.add(
RVData(name.text.toString(),desc.text.toString())
)
FirstFragment().adapter.list = dataSource().dataList; //or something like that.
FirstFragment().adapter.notifyDataSetChanged()
toNextScreen(view)
}
You need to update the Adapters internal list and then notifyDataSetChanged()
Currently I have a recyclerview adapter that gets data from the database and displays it in the dashboard fragment. Once the item is clicked, I want to pass the item id to the details fragment to get the correct items information on the detailed view. How can I pass this ID to the detailed fragment?
Dashboard Fragment
class DashboardFragment : Fragment() {
private lateinit var viewModel: DashboardViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: DashboardFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.dashboard_fragment, container, false)
val application = requireNotNull(this.activity).application
val dataSource = NumberDatabase.getInstance(application).numberDatabaseDao
val viewModelFactory = DashboardViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val dashboardViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(DashboardViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.dashboardViewModel = dashboardViewModel
val adapter = CounterAdapter(CounterListener { nightId ->
dashboardViewModel.onCountClicked(nightId)
})
binding.counterList.adapter = adapter
dashboardViewModel.counts.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
}
Adapter
class CounterAdapter (val clickListener: CounterListener): ListAdapter<Number, CounterAdapter.ViewHolder>(NightDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)!!, clickListener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(val binding: ListItemCounterBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: Number, clickListener: CounterListener) {
binding.night = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemCounterBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class CounterListener(val clickListener: (nightId: Long) -> Unit) {
fun onClick(night: Number) = clickListener(night.nightId)
}
In your recyclerview
class RecipeRecyclerAdapter(
mFragmentCommunication: FragmentCommunication
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mFragmentCommunication: FragmentCommunication? = null
init {
this.mFragmentCommunication =mFragmentCommunication
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
RecyclerView.ViewHolder {
view = LayoutInflater.from(parent.context)
.inflate(
com.broadcast.recipeslistapp.R.layout.layout_recipe_list_item,
parent,
false
)
return RecipeViewHolder(view)
}
}
override fun getItemCount(): Int {
if (mRecipes.isNullOrEmpty()) {
return 0
} else {
return mRecipes!!.size
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
mFragmentCommunication!!.PassNightId(nightId = 1) //value of nightId
}
}
interface FragmentCommunication {
fun PassNightId(nightId: Int)
}
Fragment A from where you want to send data :
class RecipeListActivity : BaseActivity(),
RecipeRecyclerAdapter.FragmentCommunication {
private lateinit var layoutManager: LinearLayoutManager
}
private fun initRecyclerView() {
mAdapter = RecipeRecyclerAdapter( this)
val itemDecorator = VerticalSpacingItemDecorator(30);
recipe_list.addItemDecoration(itemDecorator);
recipe_list.adapter = mAdapter
layoutManager = LinearLayoutManager(this);
recipe_list.layoutManager = layoutManager
}
override fun PassNightId(nightId: Int) {
val testFragment = TestFragment()
val bundle = Bundle()
bundle.putString("nightId", "$nightId")
testFragment.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.containerId,testFragment).commit()
}
}
Fragment B where you want to getData :
class TestFragment : Fragment()
{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val nightId =arguments?.getString("nightId")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.test_fragment,container,false)
}
}
This is how you can send data from on fragment to another fragment.