How to get data from Call<T> without a callback - Retrofit - Kotlin - Jetpack Compose - retrofit2

i have a method calling an API. :
interface ILoginService {
//make login
suspend fun login(payload: String): Call<Session>?
//creates payload for request body
fun createLoginPayload(login: LoginModel): String
}
override suspend fun login(payload:String):Call<Session>?{
val instance=retrofitService.getInstance()
val requestBody = payload.toRequestBody("application/json".toMediaTypeOrNull())
return instance?.requestToken(requestBody) ?: return null
}
I am returning Call to viewmodel. How i can extract data from that Call without a callback (response.enqueue() ... )???? This is the method in viewmodel:
fun loginPass() {
var result: Call<Session>?
try {
viewModelScope.launch {
result = _loginService.login(_loginService.createLoginPayload(_login))!!
val test=result
Log.d("resultado",result.toString())
}
}catch (err:Throwable){
Log.d("ERROR",err.message.toString())
}
}

Related

How to get the text from Listview to output to my external txt file

I'm new here and I'm new to programming in general.
I followed a YouTube video to make a simple todo list app for android and have since modified it into a visitors register.
I am copying people who sign in to an external txt file (that part is ok because I can grab the information as it's entered) but I have been struggling for about a week now to try to get the names of people who are signing out.
Basically a name and date/time get written to myExternalFile on btnAddTodo
is it possible in the btnDeleteDoneTodo to grab the names from deleteDoneTodos() and add them to myEternalFile??
class MainActivity : AppCompatActivity() {
private lateinit var todoAdapter: TodoAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
todoAdapter = TodoAdapter(mutableListOf())
rvTOdoItems.adapter = todoAdapter
rvTOdoItems.layoutManager = LinearLayoutManager(this)
val fileName = "log.txt"
val filePath = "Sign In\\"
val myExternalFile = File (getExternalFilesDir(filePath),fileName)
btnAddTodo.setOnClickListener {
val todoTitle = etTodoTitle.text.toString().uppercase()
val regTitle = etRegTitle.text.toString().uppercase()
if (todoTitle.isNotEmpty() && regTitle.isNotEmpty()) {
val todo = Todo(todoTitle+"\n"+regTitle)
myExternalFile.appendText(todoTitle +" "+ regTitle +" "+ todoAdapter.getCurrentDateTime() +"\n")
todoAdapter.addTodo(todo)
etTodoTitle.text.clear()
etRegTitle.text.clear()
}
}
btnDeleteDoneTodo.setOnClickListener {
todoAdapter.deleteDoneTodos()
}
}
class Todo (
val title : String,
var isChecked: Boolean = false
class TodoAdapter(
val todos: MutableList<Todo>
) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
return TodoViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.todo,
parent,
false
)
)
}
fun getCurrentDateTime() : Date {
return Calendar.getInstance().time
}
fun addTodo (todo : Todo) {
todos.add(todo)
notifyItemInserted(todos.size-1)
}
fun deleteDoneTodos () {
todos.removeAll(Todo::isChecked)
notifyDataSetChanged()
}
private fun toggleStrikeThrough(tvtodoTitle: TextView, isChecked: Boolean) {
if (isChecked) {
tvtodoTitle.paintFlags = tvtodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
} else {
tvtodoTitle.paintFlags = tvtodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
}
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val curTodo = todos[position]
holder.itemView.apply {
tvTodoTitle.text = curTodo.title
cdDone.isChecked = curTodo.isChecked
toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
cdDone.setOnCheckedChangeListener { _, isChecked ->
toggleStrikeThrough(tvTodoTitle, isChecked)
curTodo.isChecked = !curTodo.isChecked
}
}
}
override fun getItemCount(): Int {
return todos.size
}
}
I have tried appending just about every variable I think would be relevant with .toString() in the btnDeleteDoneTodos method, I have tried making separate lists in the deleteDoneTodos function, I tried making a new function that outputs a list but I have failed.
I don't know if it's even possible with the way it has all been implemented - like I said I'm very new to programming so any help would be appreciated.
Thanks
edit.
I have managed to grab a single person leaving by adding the following leavers function to the todoAdapter class:
fun leavers (tvtodoTitle: TextView, isChecked: Boolean) : String {
var going = ""
if (isChecked) going = tvtodoTitle.text.toString()
return going
}
which returns a single string (hence only one person) that I can then append to my external file. I have tried to change the function to mutable list then iterate through it in btnDeleteDoneTodo listener but it will still only return one name.
I think I'm going to can this and go with Dewerros suggestion and try to setup a local Db, if anybody does have a solution it would still be gladly received as this has annoyed me for far too long now.

I don't know why this app is shutting down(because of databinding,setOnClickListener)

class day_1 : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
private var _binding: FragmentDay1Binding? = null
private val binding get() = _binding!!
private var mediaPlayer: MediaPlayer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
binding.startbutton.setOnClickListener {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(activity, R.raw.days)
}
mediaPlayer?.start()
}
binding.pausebutton.setOnClickListener {
if (mediaPlayer?.isPlaying == true) {
mediaPlayer?.pause()
} else {
mediaPlayer?.start()
}
}
binding.stopbutton.setOnClickListener {
mediaPlayer?.stop()
mediaPlayer = null
}
fun onStop() {
super.onStop()
mediaPlayer?.release()
mediaPlayer = null
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentDay1Binding.inflate(inflater,container,false)
return binding.root
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment day_1.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
day_1().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
what is above is my code. If I click the button, application was shutting down. First, I think that Media Player is problem. but application without setOnClickListener has no problem. Please help me masters.... Is it problem that setOnClickListener is in Fragment?
And Can I amend this code? or I restart project? Perhaps If you know why this problem exist, Please tell me resolution...
You can take a look for fragment lifecycles at here https://developer.android.com/guide/fragments/lifecycle
You are setting on click listener at onCreate method, in fragments views are created at onCreateView method so you are trying to access your view before it is created. So instead of onCreate method you need to set your click listener after your binding/view is ready, for example you can set your click listener before returning binding.root at onCreateView. Also you can set your click listener at onViewCreated method.

Custom Lazy<Deferred<T>> fun leads to frame skipping and crash

I'm following the MVVM using ROOM,Retrofit,Koin DI etc. and inside of my MainFragment class i'm calling my bindUI() function that is responsible for fetching data through the viewModel asynchronously using kotlin coroutines as you can see below. Now, when i run my app it crashes almost immediately.
Here's what i tried: I placed a breakpoint inside of bindUI() and specifically on my first .await() call on val currentWeather and run the debugger. I noticed that as soon as the await call is resolved and the result is returned to the variable, the application crashes saying that Skipped 1501 frames! The application may be doing too much work on its main thread. and then Skipped 359 frames! The application may be doing too much work on its main thread.
Now, why would that be since i'm running those async calls inside of the Dispathcers.IO thread and at the moment of the crash i'm only executing one await() call?
Here's my MainFragment class:
const val UNIT_SYSTEM_KEY = "UNIT_SYSTEM"
class MainFragment(
private val weatherUnitConverter: WeatherUnitConverter
) : ScopedFragment() {
// Lazy inject the view model
private val viewModel: WeatherViewModel by viewModel()
private lateinit var unitSystem:String
private val TAG = MainFragment::class.java.simpleName
// View declarations
private lateinit var lcHourlyForecasts: LineChart
private lateinit var weeklyForecastRCV: RecyclerView
private lateinit var scrollView: NestedScrollView
private lateinit var detailsExpandedArrow:ImageView
private lateinit var detailsExpandedLayout: LinearLayout
private lateinit var dailyWeatherDetailsHeader:LinearLayout
private lateinit var settingsBtnImageView:ImageView
private lateinit var unitSystemImgView:ImageView
private lateinit var locationTxtView:TextView
// Current weather view declarations
private lateinit var currentWeatherDate:TextView
private lateinit var currentWeatherTemp:TextView
private lateinit var currentWeatherSummaryText:TextView
private lateinit var currentWeatherSummaryIcon:ImageView
private lateinit var currentWeatherPrecipProb:TextView
// Today/Details weather view declarations
private lateinit var todayHighLowTemp:TextView
private lateinit var todayWindSpeed:TextView
private lateinit var todayFeelsLike:TextView
private lateinit var todayUvIndex:TextView
private lateinit var todayPrecipProb:TextView
private lateinit var todayCloudCover:TextView
private lateinit var todayHumidity:TextView
private lateinit var todayPressure:TextView
private lateinit var todaySunriseTime:TextView
private lateinit var todaySunsetTime:TextView
// OnClickListener to handle the current weather's "Details" layout expansion/collapse
private val onCurrentWeatherDetailsClicked:View.OnClickListener = View.OnClickListener {
if(detailsExpandedLayout.visibility == View.GONE) {
detailsExpandedLayout.visibility = View.VISIBLE
detailsExpandedArrow.setImageResource(R.drawable.ic_arrow_up_black)
}
else {
detailsExpandedLayout.visibility = View.GONE
detailsExpandedArrow.setImageResource(R.drawable.ic_down_arrow)
}
}
// OnClickListener to allow navigating from this fragment to the settings one
private val onSettingsButtonClicked:View.OnClickListener = View.OnClickListener {
(activity as MainActivity).openSettingsPage()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.main_fragment, container, false)
// View initializations
scrollView = view.findViewById(R.id.nsv_main)
lcHourlyForecasts = view.findViewById(R.id.lc_hourly_forecasts)
detailsExpandedLayout = view.findViewById(R.id.ll_expandable)
detailsExpandedArrow = view.findViewById(R.id.iv_arrow)
dailyWeatherDetailsHeader = view.findViewById(R.id.current_weather_details_header)
dailyWeatherDetailsHeader.setOnClickListener(onCurrentWeatherDetailsClicked)
settingsBtnImageView = view.findViewById(R.id.settings)
settingsBtnImageView.setOnClickListener(onSettingsButtonClicked)
unitSystemImgView = view.findViewById(R.id.unitSystemImg)
locationTxtView = view.findViewById(R.id.location)
initCurrentWeatherViews(view)
initTodayWeatherViews(view)
// RCV initialization
weeklyForecastRCV = view.findViewById(R.id.weekly_forecast_rcv)
weeklyForecastRCV.adapter = WeeklyWeatherAdapter(listOf(),viewModel.preferences, this,weatherUnitConverter) // init the adapter with empty data
weeklyForecastRCV.setHasFixedSize(true)
// Disable nested scrolling to control the RCV scrolling via the parent NestedScrollView
weeklyForecastRCV.isNestedScrollingEnabled = false
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
initLineChart()
bindUI()
}
private fun SharedPreferences.stringLiveData(key: String, defValue: String): SharedPreferenceLiveData<String> {
return SharedPreferenceStringLiveData(this, key, defValue)
}
private fun bindUI() = launch(Dispatchers.Main) {
//TODO:sp get the coordinates dynamically
viewModel.setLocCoordinates(37.8267,-122.4233)
// fetch current weather
val currentWeather = viewModel.currentWeatherData.await()
// fetch weekly weather
val weeklyWeather = viewModel.weeklyWeatherEntries.await()
// fetch the location
val weatherLocation = viewModel.weatherLocation.await()
// Observe the location for changes
weatherLocation.observe(viewLifecycleOwner, Observer { location ->
if(location == null) return#Observer
launch {
updateLocation(location)
}
})
// Observe the current weather live data
currentWeather.observe(viewLifecycleOwner, Observer {currently ->
if(currently == null) return#Observer
setCurrentWeatherDate(currently.time.toDouble())
// Observe the unit system sharedPrefs live data for changes
viewModel.preferences.stringLiveData(UNIT_SYSTEM_KEY, UnitSystem.SI.name.toLowerCase(Locale.ROOT))
.observe(viewLifecycleOwner, Observer {unitSystem ->
when(unitSystem) {
UnitSystem.SI.name.toLowerCase(Locale.ROOT) -> {
setCurrentWeatherTemp(currently.temperature)
setUnitSystemImgView(unitSystem)
}
UnitSystem.US.name.toLowerCase(Locale.ROOT) -> {
setCurrentWeatherTemp(weatherUnitConverter.convertToFahrenheit(
currently.temperature
))
setUnitSystemImgView(unitSystem)
}
}
})
setCurrentWeatherSummaryText(currently.summary)
setCurrentWeatherSummaryIcon(currently.icon)
setCurrentWeatherPrecipProb(currently.precipProbability)
})
// observe the weekly weather live data
weeklyWeather.observe(viewLifecycleOwner, Observer {weatherEntries ->
if(weatherEntries == null) return#Observer
// update the recyclerView with the new data
(weeklyForecastRCV.adapter as WeeklyWeatherAdapter).updateWeeklyWeatherData(weatherEntries)
initTodayData(weatherEntries[0])
})
}
/**
* Uses the location param's lat & longt values
* to determine the selected location and updates
* the view.
*/
private suspend fun updateLocation(location: WeatherLocation) {
withContext(Dispatchers.IO) {
val geocoder = Geocoder(activity,Locale.getDefault())
try {
val addr = geocoder.getFromLocation(location.latitude,location.longitude,1)
val adobj = addr[0]
locationTxtView.text = adobj.countryName
} catch (e:IOException) {
Log.d(TAG, e.printStackTrace().toString())
}
}
}
/**
* Initializes the views for the current weather.
*/
private fun initCurrentWeatherViews(view: View) {
currentWeatherDate = view.findViewById(R.id.current_weather_date)
currentWeatherTemp = view.findViewById(R.id.current_temp_main)
currentWeatherSummaryText = view.findViewById(R.id.current_weather_summary_text)
currentWeatherSummaryIcon = view.findViewById(R.id.current_weather_summary_icon)
currentWeatherPrecipProb = view.findViewById(R.id.current_weather_precip_text)
}
/**
* Initializes the views for the Detailed Today weather view.
*/
private fun initTodayWeatherViews(view: View?) {
if(view == null) return
todayHighLowTemp = view.findViewById(R.id.today_lowHighTemp)
todayWindSpeed = view.findViewById(R.id.today_windSpeed)
todayFeelsLike = view.findViewById(R.id.today_feelsLike)
todayUvIndex = view.findViewById(R.id.today_uvIndex)
todayPrecipProb = view.findViewById(R.id.today_precipProb)
todayCloudCover = view.findViewById(R.id.today_cloudCover)
todayHumidity = view.findViewById(R.id.today_humidity)
todayPressure = view.findViewById(R.id.today_pressure)
todaySunriseTime = view.findViewById(R.id.today_sunriseTime)
todaySunsetTime = view.findViewById(R.id.today_sunsetTime)
}
private fun setUnitSystemImgView(unitSystem:String) {
val resource = when(unitSystem) {
UnitSystem.SI.name.toLowerCase(Locale.ROOT)
-> R.drawable.ic_celsius
UnitSystem.US.name.toLowerCase(Locale.ROOT)
-> R.drawable.ic_fahrenheit
else -> R.drawable.ic_celsius
}
unitSystemImgView.setImageResource(resource)
}
/**
* Links the data to the view for the Today(Details) Weather View.
*/
private fun initTodayData(weekDayWeatherEntry: WeekDayWeatherEntry) {
// Observe the unit system sharedPrefs live data for changes
viewModel.preferences.stringLiveData(UNIT_SYSTEM_KEY, UnitSystem.SI.name.toLowerCase(Locale.ROOT))
.observe(viewLifecycleOwner, Observer {unitSystem ->
when(unitSystem) {
UnitSystem.SI.name.toLowerCase(Locale.ROOT) -> {
setTodayWeatherLowHighTemp(weekDayWeatherEntry.temperatureLow,weekDayWeatherEntry.temperatureHigh)
setTodayWeatherWindSpeed(weekDayWeatherEntry.windSpeed,unitSystem)
setTodayWeatherFeelsLike(weekDayWeatherEntry.apparentTemperatureLow,weekDayWeatherEntry.apparentTemperatureHigh)
}
UnitSystem.US.name.toLowerCase(Locale.ROOT) -> {
setTodayWeatherLowHighTemp(weatherUnitConverter.convertToFahrenheit(
weekDayWeatherEntry.temperatureLow),
weatherUnitConverter.convertToFahrenheit(
weekDayWeatherEntry.temperatureHigh))
setTodayWeatherWindSpeed(weatherUnitConverter.convertToMiles(weekDayWeatherEntry.windSpeed),unitSystem)
setTodayWeatherFeelsLike(weatherUnitConverter.convertToFahrenheit(
weekDayWeatherEntry.apparentTemperatureLow)
,weatherUnitConverter.convertToFahrenheit(weekDayWeatherEntry.apparentTemperatureHigh))
}
}
})
setTodayWeatherUvIndex(weekDayWeatherEntry.uvIndex)
setTodayWeatherPrecipProb(weekDayWeatherEntry.precipProbability)
setTodayWeatherCloudCover(weekDayWeatherEntry.cloudCover)
setTodayWeatherHumidity(weekDayWeatherEntry.humidity)
setTodayWeatherPressure(weekDayWeatherEntry.pressure)
setTodayWeatherSunriseTime(weekDayWeatherEntry.sunriseTime)
setTodayWeatherSunsetTime(weekDayWeatherEntry.sunsetTime)
}
...
}
WeatherViewModel.kt:
class WeatherViewModel(
private val forecastRepository: ForecastRepository,
context:Context
) : ViewModel() {
private var mLatitude:Double = 0.0
private var mLongitute:Double = 0.0
private val appContext = context.applicationContext
// Retrieve the sharedPrefs
val preferences:SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(appContext)
// This will run only when currentWeatherData is called from the View
val currentWeatherData by lazyDeferred {
forecastRepository.getCurrentWeather(mLatitude, mLongitute)
}
val weeklyWeatherEntries by lazyDeferred {
val currentDateEpoch = LocalDate.now().toEpochDay()
forecastRepository.getWeekDayWeatherList(mLatitude, mLongitute, currentDateEpoch)
}
val weatherLocation by lazyDeferred {
forecastRepository.getWeatherLocation()
}
fun setLocCoordinates(latitude:Double,longitude:Double) {
mLatitude = latitude
mLongitute = longitude
}
}
Here's my custom Lazy<Deferred<T>> fun inside of my Delegates.kt file:
fun<T> lazyDeferred(block: suspend CoroutineScope.() -> T) : Lazy<Deferred<T>> {
return lazy {
GlobalScope.async(start = CoroutineStart.LAZY) {
block.invoke(this)
}
}
}
Here's my repository class just in case:
private const val WEEKLY_FORECAST_DAYS_COUNT = 7
/**
* The Repository class responsible
* for caching the downloaded weather data
* and for swapping between different data sources.
*/
class ForecastRepositoryImpl(
private val currentWeatherDao: CurrentWeatherDao,
private val weekDayWeatherDao: WeekDayWeatherDao,
private val weatherLocationDao: WeatherLocationDao,
private val locationProvider: LocationProvider,
private val weatherNetworkDataSource: WeatherNetworkDataSource
) : ForecastRepository {
init {
weatherNetworkDataSource.apply {
// Persist downloaded data
downloadedCurrentWeatherData.observeForever { newCurrentWeather: CurrentWeatherResponse? ->
persistFetchedCurrentWeather(newCurrentWeather!!)
}
downloadedWeeklyWeatherData.observeForever { newWeeklyWeather: WeeklyWeatherResponse? ->
persistFetchedWeeklyWeather(newWeeklyWeather!!)
}
}
}
override suspend fun getCurrentWeather(latitude:Double,longitude:Double): LiveData<CurrentWeatherEntry> {
return withContext(Dispatchers.IO) {
initWeatherData(latitude,longitude)
return#withContext currentWeatherDao.getCurrentWeather()
}
}
override suspend fun getWeekDayWeatherList(latitude: Double,longitude: Double,time:Long): LiveData<out List<WeekDayWeatherEntry>> {
return withContext(Dispatchers.IO) {
initWeatherData(latitude,longitude)
return#withContext weekDayWeatherDao.getFutureWeather(time)
}
}
override suspend fun getWeatherLocation(): LiveData<WeatherLocation> {
return withContext(Dispatchers.IO) {
return#withContext weatherLocationDao.getWeatherLocation()
}
}
private suspend fun initWeatherData(latitude:Double,longitude:Double) {
// retrieve the last weather location from room
val lastWeatherLocation = weatherLocationDao.getWeatherLocation().value
if(lastWeatherLocation == null ||
locationProvider.hasLocationChanged(lastWeatherLocation)) { // then this is the first time we are launching the app
fetchCurrentWeather()
fetchWeeklyWeather()
return
}
if(isFetchCurrentNeeded(lastWeatherLocation.zonedDateTime))
fetchCurrentWeather()
if(isFetchWeeklyNeeded())
fetchWeeklyWeather()
}
/**
* Checks if the current weather data should be re-fetched.
* #param lastFetchedTime The time at which the current weather data were last fetched
* #return True or false respectively
*/
private fun isFetchCurrentNeeded(lastFetchedTime: ZonedDateTime) : Boolean {
val thirtyMinutesAgo = ZonedDateTime.now().minusMinutes(30)
return lastFetchedTime.isBefore(thirtyMinutesAgo)
}
/**
* Fetches the Current Weather data from the WeatherNetworkDataSource.
*/
private suspend fun fetchCurrentWeather() {
weatherNetworkDataSource.fetchCurrentWeather(
locationProvider.getPreferredLocationLat(),
locationProvider.getPreferredLocationLong()
)
}
private fun isFetchWeeklyNeeded(): Boolean {
val todayEpochTime = LocalDate.now().toEpochDay()
val futureWeekDayCount = weekDayWeatherDao.countFutureWeekDays(todayEpochTime)
return futureWeekDayCount < WEEKLY_FORECAST_DAYS_COUNT
}
private suspend fun fetchWeeklyWeather() {
weatherNetworkDataSource.fetchWeeklyWeather(
locationProvider.getPreferredLocationLat(),
locationProvider.getPreferredLocationLong()
)
}
/**
* Caches the downloaded current weather data to the local
* database.
* #param fetchedCurrentWeather The most recently fetched current weather data
*/
private fun persistFetchedCurrentWeather(fetchedCurrentWeather:CurrentWeatherResponse) {
// Using a GlobalScope since a Repository class doesn't have a lifecycle
GlobalScope.launch(Dispatchers.IO) {
// cache the data
currentWeatherDao.upsert(fetchedCurrentWeather.currentWeatherEntry)
weatherLocationDao.upsert(fetchedCurrentWeather.location)
}
}
/**
* Caches the downloaded weekly weather data to the local
* database.
* #param fetchedWeeklyWeather The most recently fetched weekly weather data
*/
private fun persistFetchedWeeklyWeather(fetchedWeeklyWeather: WeeklyWeatherResponse) {
fun deleteOldData() {
val time = LocalDate.now().toEpochDay()
weekDayWeatherDao.deleteOldEntries(time)
}
GlobalScope.launch(Dispatchers.IO) {
deleteOldData()
val weekDayEntriesList = fetchedWeeklyWeather.weeklyWeatherContainer.weekDayEntries
weekDayWeatherDao.insert(weekDayEntriesList)
}
}
}
EDIT: Here's the crash log i found out about earlier today:
2020-04-13 01:43:48.628 26875-26904/com.nesoinode.flogaweather E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-2
Process: com.nesoinode.flogaweather, PID: 26875
java.lang.NullPointerException: Attempt to invoke virtual method 'int com.nesoinode.flogaweather.model.db.entity.WeatherLocation.getId()' on a null object reference
at com.nesoinode.flogaweather.model.db.dao.WeatherLocationDao_Impl$1.bind(WeatherLocationDao_Impl.java:34)
at com.nesoinode.flogaweather.model.db.dao.WeatherLocationDao_Impl$1.bind(WeatherLocationDao_Impl.java:26)
at androidx.room.EntityInsertionAdapter.insert(EntityInsertionAdapter.java:63)
at com.nesoinode.flogaweather.model.db.dao.WeatherLocationDao_Impl.upsert(WeatherLocationDao_Impl.java:52)
at com.nesoinode.flogaweather.model.repository.ForecastRepositoryImpl$persistFetchedCurrentWeather$1.invokeSuspend(ForecastRepositoryImpl.kt:131)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
It's pointing to this part of my repository class as the root cause. I can't tell why though.
private fun persistFetchedCurrentWeather(fetchedCurrentWeather:CurrentWeatherResponse) {
// Using a GlobalScope since a Repository class doesn't have a lifecycle
GlobalScope.launch(Dispatchers.IO) {
// cache the data
currentWeatherDao.upsert(fetchedCurrentWeather.currentWeatherEntry)
weatherLocationDao.upsert(fetchedCurrentWeather.location)
}
}
UPDATE #2:
CurrentWeatherEntry :
const val CURRENT_WEATHER_ID = 0
#Entity(tableName = "current_weather")
data class CurrentWeatherEntry(
val time: Long, // epoch timestamp
val icon: String,
val summary: String,
val precipProbability: Double,
val temperature: Double
) {
#PrimaryKey(autoGenerate = false)
var id:Int = CURRENT_WEATHER_ID
}
WeatherLocation:
const val WEATHER_LOCATION_ID = 0
#Entity(tableName = "weather_location")
data class WeatherLocation(
val latitude: Double,
val longitude: Double,
val timezone: String
) {
#PrimaryKey(autoGenerate = false)
var id:Int = WEATHER_LOCATION_ID
private var epochTimeVal:Long = 0
val zonedDateTime:ZonedDateTime
get() {
val instant = Instant.ofEpochMilli(this.epochTimeVal)
val zoneId = ZoneId.of(timezone)
return ZonedDateTime.ofInstant(instant,zoneId)
}
fun setEpochTimeVal(time:Long) {
this.epochTimeVal = time}
fun getEpochTimeVal() : Long = epochTimeVal
}
and CurrentWeatherResponse:
data class CurrentWeatherResponse(
// Tells GSON that the "currently" field of the JSON returned by the
// API should be tied with our CurrentWeatherEntry data class
#SerializedName("currently")
val currentWeatherEntry: CurrentWeatherEntry,
#Embedded
val location: WeatherLocation
) {
init {
location.setEpochTimeVal(currentWeatherEntry.time)
}
}
According to the further diagnosis you made, the problem is unrelated to lazyDeferred, blocking the main thread, or coroutines in general. Your CurrentWeatherDao sometimes returns a CurrentWeatherResponse with location == null.
You never specified where forecastRepository.getWeatherLocation() should be executed so it gets executed on the dispatcher of your bindUI function which is Dispatchers.Main.
This means the request blocks your UI thread and causes the warning you're seeing in the log.
You need to specify that it executes on a separate dispatcher so the UI can continue updating normally:
lazyDeferred {
withContext(Dispatchers.IO) {
forecastRepository.getWeatherLocation()
}
}
As a separate issue, your lazyDeferred is slightly redundant in that it is "double"-lazy. You could remove the outer Lazy<T> and it would still work exactly the same way, or remove the start = CoroutineStart.LAZY and have the result arrive slightly sooner. (This essentially depends on whether the request starts when the Lazy is resolved, or when Deferred.await is called)

How to Pagination using retrofit to fetch next items from API on Scrolling in kotlin?

I want to make my recyclerview paginationscrolling using retrofit.
I already complete get Json data using retrofit. It means interface API is correct from API Document.
However, if I loaded more 20items. can not scroll more items in Client.
when I checked Server data. per one page can get maximum 20items.
For example, if I loaded 25items in my recyclerview.
page 0: 20, page 1: 5.
if I want scrolling all items, How can I make paginationscrolling for retrofit??
check some my code and help me..
Response
Interface API
#GET("/store/cart/mine")
fun getCart(#Header("Authorization") token: String?, #Query("page") page:Int): Call<CartResponse>
CartViewActivity
class CartViewActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefreshListener {
private val lastVisibleItemPosition: Int
get()= LinearLayoutManager.HORIZONTAL
private lateinit var scrollListener: RecyclerView.OnScrollListener
lateinit var mAdapter: CartItemRecyclerAdapter
#RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cart_view)
val page = 0
val token = SharedPreference.getTokenInfo(this)
Client.retrofitService.getCart(token,page).enqueue(object :Callback<CartResponse> {
override fun onResponse(call: Call<CartResponse>, response: Response<CartResponse>) {
if (response?.isSuccessful == true) {
swipeRefreshLo.setOnRefreshListener(this#CartViewActivity)
showdata(response.body()?.docs!!)
}else if(response?.isSuccessful==false) {
val er = Gson().fromJson(response.errorBody()?.charStream(), ErrorResponse::class.java)
if (er.code==60202) {
}
}
}
override fun onFailure(call: Call<CartResponse>, t: Throwable) {
}
})
}
private fun showdata(results: MutableList<cartDocs>) {
recycler_view.apply {
mAdapter=CartItemRecyclerAdapter(context,context as Activity, results)
recycler_view.adapter=mAdapter
recycler_view.layoutManager=LinearLayoutManager(context)
setRecyclerViewScrollListener()
}
}
private fun setRecyclerViewScrollListener() {
scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val totalItemCount = recyclerView.layoutManager!!.itemCount
if(totalItemCount == lastVisibleItemPosition + 1) {
Log.d("MyTAG", "Load new list")
recyclerView.removeOnScrollListener(scrollListener)
}
}
}
}
override fun onRefresh() {
swipeRefreshLo.isRefreshing = false
}
}
Firstly, you need to modify your adapter to be able to update existing data, instead of creating new adapter every time you fetch new data.
Adapter should be initialised before getting the data, to be able to call methods on it.
You should create a method inside the adapter, something like
fun updateData(results: MutableList<cartDocs>) {
dataSet.addAll(results)
notifyItemRangeInserted(start, newItemsSize)
}
Then, we you get response from server in onSuccess() you should call method above, and increment page, so the next time you load data, you get new items.
Data should be fetched first time when screen loads (page will be 0), and then when user scrolls to bottom of RV ().

C# Parse how to wait until Query returns a value

I'm trying to retrieve user data from Parse (xamarin.ios using c#). I'm using an async method with await. My challenge is,each time I navigate to the tableView in the app, which should populate the user data in question,the table is always empty.
I would like to wait until the results have been returned before proceeding with the other portion of code.I have tried to use the ContinueWith() function but constantly ran into a build error -
Cannot implicitly convert type 'void' to System.Collections.Generic.IEnumerable<Parse.ParseObject>
My Questions:
Is this the best way to wait for the result?
How do I solve the build error?
Here is my current implementation:
public async void retrieveData(string username)
{
try
{
this.requests.ClearRequests();
refreshed = false;
var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);
IEnumerable<ParseObject> results = await query.FindAsync().ContinueWith(t =>{
if(results != null)
{
foreach(ParseObject parseObject in results)
{
UserRequest request = new UserRequest();
request.objectId = parseObject.ObjectId;
request.make = parseObject.Get<string> ("item1");
request.model = parseObject.Get<string> ("item2");
request.year = parseObject.Get<string> ("item3");
request.userName = parseObject.Get<string> ("username");
this.requests.addRequest (request);
}
refreshed = true;
}
});
}
catch(ParseException e) {
Console.WriteLine (e.Message + e.StackTrace);
}
}
You shouldn't need a ContinueWith...that's what the await should handle.
await waits on a Task and then brings back the result with the proper return type. ContinueWith returns a Task, so you would have to grab the Result from the task to make it usable.
For more on this type of thing, you may want to check out Difference between await and ContinueWith
You can try something like this.
public async void retrieveData(string username, )
{
try
{
this.requests.ClearRequests();
refreshed = false;
var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);
IEnumerable<ParseObject> results = await query.FindAsync();
if(results != null)
{
foreach(ParseObject parseObject in results)
{
UserRequest request = new UserRequest();
request.objectId = parseObject.ObjectId;
request.make = parseObject.Get<string> ("item1");
request.model = parseObject.Get<string> ("item2");
request.year = parseObject.Get<string> ("item3");
request.userName = parseObject.Get<string> ("username");
this.requests.addRequest (request);
}
refreshed = true;
}
//This is your refresh method for your TableView
this.RefreshTableView();
//or, if in iOS
NSNotificationCenter.DefaultCenter.PostNotificationName("resultsRetrieved", null);
}
catch(ParseException e) {
Console.WriteLine (e.Message + e.StackTrace);
}
}
To show the results in the tableView, I would recommend moving the refreshing of the tableView to a separate method that gets triggered synchronously after the results have been retrieved and parsed. This is shown with the this.RefreshTableView() call above.
If in iOS on Xamarin, another option is to post a notification to the NSNotificationCenter (the Xamarin documentation for which is here). Use the PostNotificationName part seen above instead and then add an observer in the ViewControllers that you want to be dependent on the data. This is done as follows:
Make a notificationToken object:
NSObject notificationToken;
Then in your setup method (you could put this inside of your ViewDidLoad):
void Setup ()
{
notificationToken = NSNotificationCenter.DefaultCenter.AddObserver ("resultsRetrieved", RefreshData);
}
Make your RefeshData method:
void RefreshData (NSString notifString)
{
this.tableView.ReloadData();
}
And then, make sure you dispose of the notification observer when you tear down the class
void Teardown ()
{
NSNotificationCenter.DefaultCenter.RemoveObserver (notificationToken);
}
I had a similar issue so started using callbacks. I'm using them in Xamarin.Android, pretty sure they're available in Xamarin.iOS.
Method that starts the task method - Note I am passing in a method of this class as a parameter
private async void updatedData()
{
await Utils.DataTasks.getNewLiveTips(populateTipsList);
}
Method that calls for data from server
public class DataTasks
{
public static async Task getAllData(Action<IEnumerable<ParseObjects>> callback) {
var query = new ParseQuery<ParseObjects>().OrderByDescending("updatedAt").Limit(5);
IEnumerable<ParseObjects> parseTips = await query.FindAsync();
foreach (var tip in parseTips)
{
// Save data to DB if needed
}
callback(parseTips);
}
Method I passed as parameter in the first instance is now called
private void populateTipsList(IEnumerable<ParseObjects> results)
{
mAdapter = new TipAdapter(this.Activity, results);
mRecyclerView.SetAdapter(mAdapter);
refresher.Refreshing = false;
}

Resources