Issue
I'm making weather app using 5 days openweathermap api. My problem is that after request i'm getting big array with 40 weather results for 5 days every 3 hours. I need to make my output show 5 days in view pager for each day of this 5 days so i need to split this json result to array so i can take only what i need. My view pager adapter:
class PagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
FragmentStateAdapter(fragmentManager, lifecycle) {
private var data: List<MyList> = listOf()
override fun getItemCount() = data.size
fun submitData(data: List<MyList>) {
this.data = data
notifyDataSetChanged()
}
override fun createFragment(position: Int): Fragment {
return if (position in data.indices) {
DayWeatherFragment().apply {
arguments = Bundle().apply {
putSerializable("TAG", data[position])
}
}
} else Fragment()
}
}
Fragment in view pager. I will fill all text views here:
class DayWeatherFragment : Fragment() {
private lateinit var binding: FragmentDayWeatherBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentDayWeatherBinding.inflate(inflater)
// Inflate the layout for this fragment
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val obj: MyList = arguments?.getSerializable("TAG") as MyList
binding.txtMorningTemp.text = obj.main.temp.toString()
}
}
This is the fragment where my view pager places:
class FiveDaysForecastFragment : Fragment() {
private lateinit var binding: FragmentFiveDaysForecastBinding
private val viewModel: FiveDaysForecastViewModel by viewModels()
private val adapter by lazy(LazyThreadSafetyMode.NONE) { PagerAdapter(childFragmentManager, lifecycle) }
@ExperimentalSerializationApi
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentFiveDaysForecastBinding.inflate(inflater)
binding.viewPager.adapter = adapter
(activity as AppCompatActivity).setSupportActionBar(binding.toolbar)
val cityId = this.arguments?.getInt(CITY_ID) ?: 0
viewModel.getFiveDaysForecast(cityId)
return binding.root
}
@ExperimentalSerializationApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
tab.text = "${position+1}"
}.attach()
viewModel.fiveDaysForecast.observe(viewLifecycleOwner) {
adapter.submitData(it)
}
binding.toolbar.setNavigationOnClickListener {
parentFragmentManager.popBackStack()
}
}
}
And view model for this fragment:
class FiveDaysForecastViewModel : ViewModel() {
private val _fiveDaysForecast: MutableLiveData<List<MyList>> = MutableLiveData()
val fiveDaysForecast: LiveData<List<MyList>> = _fiveDaysForecast
@ExperimentalSerializationApi
fun getFiveDaysForecast(cityId: Int) {
RetrofitClient.api
.getFiveDaysForecast(cityId = cityId, unit = "metric", lang = "ru")
.enqueue(object : Callback<FiveDaysForecastResult> {
override fun onResponse(
call: Call<FiveDaysForecastResult>,
response: Response<FiveDaysForecastResult>
) {
if (response.isSuccessful) {
response.body()?.let {
val list = it.list.cut()
_fiveDaysForecast.postValue(list)
Timber.d("Searching for: $it")
}
} else {
Timber.d("Can't get city")
}
}
override fun onFailure(call: Call<FiveDaysForecastResult>, t: Throwable) {
Timber.d("Failure request")
}
})
}
private fun List<MyList>.cut() = if (this.size > 5) this.subList(0, 5) else this
}
WeatheForecastResult:
@Serializable
data class FiveDaysForecastResult(
val cod: String,
val message: Int,
val cnt: Int,
val list: List<MyList>,
val city: City
)
MyList:
@Serializable
data class MyList(
val dt: Int,
val main: Main,
val weather: List<Weather>,
val clouds: Clouds,
val wind: Wind,
val visibility: Int,
val pop: Double,
val sys: Sys,
val dt_txt: String,
): java.io.Serializable
Solution
I would suggest to use a map that uses dates as keys and a list of Weather object as values. This way each date will have its weather entries and you can handle it from there.
A change in your LiveData type is required:
private val _fiveDaysForecast: MutableLiveData<Map<LocalDateTime, List<MyList>>> = MutableLiveData()
val fiveDaysForecast: LiveData<Map<LocalDateTime, List<MyList>>> = _fiveDaysForecast
And in your response parsing:
response.body()?.let {
val weatherByDate = mutableMapOf<LocalDateTime, MutableList<Weather>>()
it.list.forEach { weatherItem ->
val date = LocalDateTime.ofEpochSecond(weatherItem.dt, 0, ZoneOffset.ofTotalSeconds(it.city.timezone))
weatherByDate.getOrPut(date, { mutableListOf() }).add(weatherItem)
}
_fiveDaysForecast.postValue(weatherByDate)
Now you have a map that have a weather entries for each date. Use it to get the 5 days you need.
Answered By - gioravered
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.