I'm trying to use paging with retrofit. But i have to make more than one calls for filtering or changing the rover(it is a nasa pictures app). But my app making Api call every second and that crashs it. I tried it with livedata also with flow but it didnt work.
There are three fragment execute same prosesses in tab layout with View Pager these are curiosityFragment, SpiritFragment, OpportunityFragment
object RetrofitInstance {
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi)).baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
val api : SimpleApi by lazy {
retrofit.create(SimpleApi::class.java)
}}
interface SimpleApi {
#GET("mars-photos/api/v1/rovers/{name}/photos?sol=1000&api_key=DEMO_KEY")
fun getPhotos(#Path("name") name : String,
#Query("per_page")per_page:Int?,
#Query("page") page: Int):
Single<AllPhotos>
class DataSource(name:String) : RxPagingSource<Int,Photo>(){
var name :String
init{
this.name = name
}
override fun getRefreshKey(state: PagingState<Int, Photo>): Int? {
return null
}
override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Photo>> {
val page = params.key ?:1
return RetrofitInstance.api.getPhotos(name,params.loadSize,page)
.subscribeOn(Schedulers.io())
.map {toLoadResult(it,page)}
.onErrorReturn { LoadResult.Error(it) }
}
private fun toLoadResult(data: AllPhotos,page : Int):LoadResult<Int,Photo>{
return LoadResult.Page(
data = data.photos,
prevKey = if(page==1) null else page-1,
nextKey = page.plus(1)
)
}
}
class ViewModelDataSource (application: Application):AndroidViewModel(application) {
fun getPhotosLiveData(name : String):LiveData<PagingData<Photo>>{
return Pager(
config = PagingConfig(pageSize = 10),
pagingSourceFactory = {
DataSource(name)
},
).liveData
}
}
class CuriosityFragment : Fragment() {
private lateinit var binding:FragmentCuriosityBinding
lateinit var mApiViewModel : ViewModelDataSource
private lateinit var curiosityAdapter: AdapterCuriosity
var hasLoadedOnce = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentCuriosityBinding.inflate(inflater,container,false)
mApiViewModel = ViewModelProvider(this).get(ViewModelDataSource::class.java)
setupRecyclerView()
loaddata()
return binding.root
}
fun setupRecyclerView(){
curiosityAdapter = AdapterCuriosity()
binding.recyclerViewCriosity.apply {
adapter = curiosityAdapter
layoutManager = LinearLayoutManager(requireContext())
}
}
fun loaddata(){
mApiViewModel.getPhotosLiveData("curiosity").observe(viewLifecycleOwner, Observer {
curiosityAdapter.submitData(this.lifecycle,it)})
}
class OpportunityFragment : Fragment() {
private lateinit var binding: FragmentOppurtunityBinding
private lateinit var mApiViewModel: ViewModelDataSource
private lateinit var recyclerView: RecyclerView
private lateinit var opportunityAdapter:AdapterOpportunity
var hasLoadedOnce = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentOppurtunityBinding.inflate(inflater, container, false)
recyclerView = binding.recyclerViewOpportunity
mApiViewModel = ViewModelProvider(this).get(ViewModelDataSource::class.java)
setupRecyclerView()
loaddata()
setHasOptionsMenu(true)
return binding.root
}
fun setupRecyclerView(){
opportunityAdapter = AdapterOpportunity()
binding.recyclerViewOpportunity.apply {
adapter = opportunityAdapter
layoutManager = LinearLayoutManager(requireContext())
}
}
fun loaddata(){
mApiViewModel.getPhotosLiveData("opportunity").observe(viewLifecycleOwner, Observer {
opportunityAdapter.submitData(this.lifecycle,it)})
}
class SpiritFragment : Fragment() {
private lateinit var binding: FragmentSpiritBinding
private lateinit var mApiViewModel : ViewModelDataSource
private lateinit var spiritAdapter:AdapterSpirit
lateinit var recyclerView: RecyclerView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentSpiritBinding.inflate(inflater, container, false)
recyclerView = binding.recyclerViewSpirit
mApiViewModel = ViewModelProvider(this).get(ViewModelDataSource::class.java)
setupRecyclerView()
loaddata()
setHasOptionsMenu(true)
return binding.root
}
fun setupRecyclerView(){
spiritAdapter = AdapterSpirit()
binding.recyclerViewSpirit.apply {
adapter = spiritAdapter
layoutManager = LinearLayoutManager(requireContext())
}
}
fun loaddata(){
mApiViewModel.getPhotosLiveData("spirit").observe(viewLifecycleOwner, Observer {
spiritAdapter.submitData(this.lifecycle,it)})
}
Related
Hi I am trying to link my sharedpreference manager via dagger and inject it into my fragment but It keeps saying it has not been initalized yet how would I go around to fix this?
Code snippet:
lass HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding
private lateinit var viewModel: HomeFragmentVM
#Inject
lateinit var spManager: SPManager
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)
val repository = Repository()
val viewModelFactory = HomeFragmentVMFactory(repository)
viewModel = ViewModelProvider(this, viewModelFactory)[HomeFragmentVM::class.java]
getUserList()
}
private fun getUserList() {
var retrofit = RetrofitClient.getInstance()
var apiInterface = retrofit.create(SimpleApi::class.java)
lifecycleScope.launchWhenCreated {
try {
val response = apiInterface.getPost()
if (response.isSuccessful) {
val products = response.body()!!.data.toString()
if (response.body()?.data?.size!! <= 0) {
} else {
if (spManager.getProductID().isEmpty())
{
spManager.saveProductID(response.body()!!.productid.toLong())
}
txtData.text = response.body()?.data.toString()
}
} else {
}
}catch (Ex: Exception){
Log.e("Error",Ex.localizedMessage)
}
}
}
}
The error does not cause a crash to the app but it comes up in my logs as lateinit property spManager has not been initalized.
I am a newcomer in Kotlin and synchronous programming.
I have code:
open class MyFragment : DialogFragment() {
private var fragmentBinding: FragmentBinding? = null
private var resultList: List<MyObject> = ArrayList()
private var list = ArrayList<MyObjectItem>()
private lateinit var adapter: MynAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
resultList = getActivity.loadObjects()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentBinding = FragmentBinding.inflate(inflater, container, false)
lifecycleScope.launch {
getListData()
initAdapter()
}
return fragmentBinding?.root
}
private fun initAdapter() {
val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(requireContext())
fragmentBinding?.recyclerView?.layoutManager = layoutManager
adapter = MyAdapter(list, requireContext(), this, this)
fragmentBinding?.recyclerView?.adapter = adapter
}
private fun getListData() {
for (value in resultList) {
list.add(
MyObjectItem(
value.title!!,
value.numbert!!,
)
)
}
}
as result I get empty list in UI. But in debug I see, that from loadObjects() method I get not empty list. I understand , that it works in debug mode only because I stop execution of UI thread on my breakpoint and I should set up the view (namely calling initAdapter() on the UI thread, not in my worker thread. But I don't understand, how can I do this....
Here is the simple example of the adapter. You can use addAll() function of the adapter to add the data into the adapter.
public class SenListAdapter() :
RecyclerView.Adapter<SenListAdapter.MyViewHolder>() {
var data: MutableList<QuizResponse.Datum> = ArrayList()
var itemListener: EventListener? = null
var inflater: LayoutInflater? = null
private var onLoadMoreListener: OnLoadMoreListener? = null
var mData = ""
var context:Context?=null
constructor(context: Context, quizType: String) : this() {
this.quizType = quizType
this.context=context
}
override fun getItemViewType(position: Int): Int {
return position
}
fun addAll(mData: List<QuizResponse.Datum>?) {
data.clear();
data.addAll(mData!!)
notifyDataSetChanged()
}
fun clear() {
data.clear()
notifyDataSetChanged()
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): SenListAdapter.MyViewHolder {
inflater = LayoutInflater.from(parent.context)
val itemHomeSenBinding = DataBindingUtil.inflate<ItemHomeSenBinding>(
inflater!!,
R.layout.item_home_sen, parent, false
)
return MyViewHolder(itemHomeSenBinding)
}
override fun onBindViewHolder(holder: SenListAdapter.MyViewHolder, position: Int) {
populateItemRows(holder, position)
}
private fun populateItemRows(holder: SenListAdapter.MyViewHolder, position: Int) {
var item = data.get(position)
holder.itemHomeSenBinding.tvQuizName.text = item.name
holder.itemHomeSenBinding.tvCost.text = "₹ " + item.entryFee.toString()
holder.itemHomeSenBinding.tvEstimatedTime.text = item.time
}
override fun getItemCount(): Int {
return data.size
}
inner class MyViewHolder(var itemHomeSenBinding: ItemHomeSenBinding) :
RecyclerView.ViewHolder(
itemHomeSenBinding.root
) {
init {
setEventlistener(itemListener)
}
}
interface EventListener {
fun onClick(position: Int, item: QuizResponse.Datum?)
}
fun setEventlistener(onItemClick: EventListener?) {
itemListener = onItemClick
}
}
To set the adapter just use the object of recycler view :
var adapter=SenListAdapter()
adapter.addAll(<Your List>)
recyclerView.setAdapter(adapter)
resultList = getActivity.loadObjects() - I suppose this is your suspend (network) call. This shouldn't be called from Main thread, move this call in coroutine launcher, and when is done you can init your adapter from Main thread.
Try this:
open class MyFragment : DialogFragment() {
private var fragmentBinding: FragmentBinding? = null
private var list = ArrayList<MyObjectItem>()
private lateinit var adapter: MynAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentBinding = FragmentBinding.inflate(inflater, container, false)
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO){
val res = getActivity.loadObjects()
withContext(Dispatchers.Main){
getListData(res)
initAdapter()
}
}
return fragmentBinding?.root
}
private fun initAdapter() {
val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(requireContext())
fragmentBinding?.recyclerView?.layoutManager = layoutManager
adapter = MyAdapter(list, requireContext(), this, this)
fragmentBinding?.recyclerView?.adapter = adapter
}
private fun getListData(res:List<MyObject>) {
for (value in res) {
list.add(
MyObjectItem(
value.title!!,
value.numbert!!,
)
)
}
}
Maybe your issue is different, but if you are going to load data asynchronous you need to use at least RecyclerView.Adapter#notifyDataSetChanged() (You can get better results using notifyItemChanged methods but those are more advanced).
Your code should look similar to:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentBinding = FragmentBinding.inflate(inflater, container, false)
initAdapter()
lifecycleScope.launch {
adapter.list = getListData()
adapter.notifyDataSetChanged() // Notify changes in the adapter
}
return fragmentBinding?.root
}
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
class FeedRecyclerAdapter (private val postList : ArrayList<Post>) : RecyclerView.Adapter<FeedRecyclerAdapter.PostHolder>() {
class PostHolder(val binding: FragmentDataBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostHolder {
val binding = FragmentDataBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return PostHolder(binding)
}
override fun onBindViewHolder(holder: PostHolder, position: Int) {
holder.binding.verimText.text = postList.get(position).lsi
}
override fun getItemCount(): Int {
return postList.size
}
Here is the code written for recyclerView.
private lateinit var firestore: FirebaseFirestore
private lateinit var auth: FirebaseAuth
private var _binding: FragmentDataBinding? = null
private val binding get() = _binding!!
private lateinit var postArrayList : ArrayList<Post>
private lateinit var feedAdapter : FeedRecyclerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
auth = Firebase.auth
firestore = Firebase.firestore
postArrayList = ArrayList<Post>()
feedAdapter = FeedRecyclerAdapter(postArrayList)
getData()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentDataBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
private fun getData(){
firestore.collection("Posts").addSnapshotListener { value, error ->
if (error!=null){
Toast.makeText(requireContext(),error.localizedMessage,Toast.LENGTH_SHORT).show()
}else{
if (value !=null){
if (!value.isEmpty){
val documents = value.documents
for (document in documents){
val araziBoyutu = document.get("Arazi Boyutu") as String
val araziEgimi = document.get("Arazi Eğimi") as String
val panelBoyutu = document.get("Panel Boyutu") as String
val panelSayisi = document.get("Panel Sayisi") as String
val sehir = document.get("Şehir") as String
val post = Post(panelSayisi,panelBoyutu,araziEgimi,araziBoyutu,sehir)
postArrayList.add(post)
recyclerView.adapter = FeedRecyclerAdapter(postArrayList)
}
feedAdapter.notifyDataSetChanged()
}
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerView.adapter = feedAdapter
recyclerView.layoutManager = LinearLayoutManager(activity)
The code here is the part where I define the RecylcerView and save the information.
I can pull the data, I can see it on firebase, when I print it with println, I can read it in the console, I can go to the page where the text should be written, but I can't see this data in the verimText TextView I'm trying to print.
Try to call notifyDataSetChanged() or notifyItemRangeInserted() after adding post.
Recyclerview should be declared inside onViewCreated Method
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById(R.id.recyclerView) as RecyclerView
recyclerView.layoutManager = LinearLayoutManager(activity)
recyclerView.adapter = FeedRecyclerAdapter(postArrayList)
}
move this line
recyclerView.adapter = FeedRecyclerAdapter(postArrayList)
to
val post = Post(panelSayisi,panelBoyutu,araziEgimi,araziBoyutu,sehir)
postArrayList.add(post)
recyclerView.adapter = FeedRecyclerAdapter(postArrayList)//move to here
this is my Retrofit retrieve code :
the error came from this line ==>> val respon = response.body()!!
( java.lang.NullPointerException
at com.xfath.hormart.fragments.homechildfragments.ChildHomeFragment$getProducts$1.onResponse(ChildHomeFragment.kt:124 )
class ChildHomeFragment : Fragment(){
private lateinit var s: SharedPreference
private var listImageSliderHomes: ArrayList<ImageSliderHome> = ArrayList()
private var listProducts: ArrayList<Products> = ArrayList()
private lateinit var sliderView: SliderView
private lateinit var rvProductBaru: RecyclerView
private lateinit var rvElektronik: RecyclerView
private lateinit var rvPribadi: RecyclerView
private lateinit var ivNotif: ImageButton
private lateinit var uidhome: TextView
private lateinit var svhome: NestedScrollView
private lateinit var mSearchView: SearchView
private lateinit var mSwipeRefreshLayout: SwipeRefreshLayout
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
getSlideImage()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_child_home, container, false)
init(view)
getProducts()
swipeRefresh()
s = SharedPreference(activity!!)
return view
}
private fun getProducts() {
ApiConfig.instanceRetrofit.getproduct().enqueue(object : Callback<ResponseModel> {
override fun onResponse(call: Call<ResponseModel>, response: Response<ResponseModel>) {
val respon = response.body()!!
if (respon.success == 1) {
listProducts = respon.products
displayProduct()
}
}
override fun onFailure(call: Call<ResponseModel>, t: Throwable) {
Log.v(TAG, "Error:" + t.message)
}
})
}
private fun displayProduct() {
val layoutManager = LinearLayoutManager(activity)
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
rvProductBaru.adapter = ProductAdapter(listProducts)
rvProductBaru.layoutManager = layoutManager
}
}
thanks in advance..
This is meant that the response.body() returns null.
You should always do null checking for it or you can use let to avoid NPE.
PS: Avoid using !! in kotlin as it may throw NPE when the variable is null.
response.body()?.let { respon ->
if (respon.success == 1) {
listProducts = respon.products
displayProduct()
}
}