Search code examples
androidkotlinretrofit

Empty data is coming from retrofit


I'm writing an application for currencies. Empty data is received (Json is normal). I tried to debug it, but I didn’t understand where and what the error was. I would be grateful if you can help me understand where the error or errors are. Here's my code:

ApiService:

interface ApiService {

    @GET("daily_json.js")
    suspend fun getValute(): Response<Valute>
}

RetrofitInstance:

object RetrofitInstance {

    private val retrofit by lazy {
        Retrofit.Builder().baseUrl("https://www.cbr-xml-daily.ru/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    val api: ApiService by lazy {
        retrofit.create(ApiService::class.java)
    }
}

Repository:

class Repository {

    suspend fun getVal(): Response<Valute>{
        return RetrofitInstance.api.getValute()
    }

}

ViewModel:

class StartViewModel: ViewModel() {

    var repo = Repository()
    val moneyList: MutableLiveData<Response<Valute>> = MutableLiveData()


    fun getMoney(){
        viewModelScope.launch {
            moneyList.value = repo.getVal()
        }
    }
}

Fragment:

       class StartFragment : Fragment() {
        private lateinit var _binding: FragmentStartBinding
        private val binding get() = _binding!!
        private val adapter = StartAdapter()
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            _binding = FragmentStartBinding.inflate(inflater, container, false)
            val view = binding.root
    
    
            return view
    
        }
    
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            binding.rvStart.adapter = adapter
            val viewModel = ViewModelProvider(this).get(StartViewModel::class.java)
            viewModel.getMoney()
            viewModel.moneyList.observe(viewLifecycleOwner,{list ->
                list.body()?.let { adapter.setList(it) }
            })
}

Adapter:

class StartAdapter: RecyclerView.Adapter<StartAdapter.StartHolder>() {

    var listValute = emptyList<Valute>()

class StartHolder(item: View): RecyclerView.ViewHolder(item) {

    val binding = ItemBinding.bind(item)

    fun bind(valute: Valute) = with(binding){

        nameMoney.text = valute.CharCode
        purchase.text = valute.Value.toString()

    }
}

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StartHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
        return StartHolder(view)
    }

    override fun getItemCount(): Int {
        return listValute.size
    }

    override fun onBindViewHolder(holder: StartHolder, position: Int) {
        holder.bind(listValute[position])
    }

    fun setList(valute: Valute){
        listValute = listOf(valute)
        notifyDataSetChanged()
    }
}

I'm attaching all the code you might need.

Update I forgot to add DataClass

data class Valute(
    val CharCode: String,
    val ID: String,
    val Name: String,
    val Nominal: Int,
    val NumCode: String,
    val Previous: Double,
    val Value: Double
): Serializable

Solution

  • Your data model does not match what is returned from the API. Your Valute (data) class is an element of the map (which is under key "Valute").

    So first create a generic model for the answer, for example something like this:

    data class ValuteResponse(
        @SerializedName("Date")
        val date: String,
    
        @SerializedName("PreviousDate")
        val previousDate: String,
    
        @SerializedName("PreviousURL")
        val previousURL: String,
    
        @SerializedName("Timestamp")
        val timestamp: String,
    
        @SerializedName("Valute")
        val valute: Map<String, Valute>,
    )
    

    Here in model class I use com.google.gson.annotations.SerializedName annotation to indicate what the field is called in the received response (JSON).

    Then use it (in ApiService) as a response model from the API:

    @GET("daily_json.js")
    suspend fun getValute(): Response<ValuteResponse>
    

    There are many websites that can create classes for you from your existing JSON:

    Json structure: enter image description here