How to split JSON into array?

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

enter image description here

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

Leave a Comment