Periodically fetch data from API in the background and updat
2024-04-17 13:45

I want to have an app that periodically fetches data from an API and update the UI with the fetched data.

I'm fairly new to Android development. I looked around and found out(altough I might as well be missing something) that I could use WorkManager to fetch the data in the background and I can periodically run it with PeriodicWorkRequest in the MainActivity or ViewModel.

However, my problem is that I couldn't figure out how to pass the fetched data from Worker. The data is a JSON response that contains list of objects, let's say List< WeatherData>. I saw that you could pass outputData to Result.success() by using key value pair with workerDataOf but I think this is only suitable for lightweight data and primitive types.

How can I pass this data that I fetch in the worker to update the UI? OR is there any other alternative to achieve my goal?

I want to achieve this by following best practices and keep separation concerns i.e. not using viewModel in the worker.



Answer 1 :

So, Here is an example to have a periodic work every 15 mins(You can set your own interval).

NOTE: minimum interval for periodic work is 15 mins. So even if you set 1 minute, the work will run every 15 mins only.

So in below example i am calling an api in the worker class in background the setting the api data in the fragment by converting the response into an jsonfile and then receiving the broadcast in fragment. And the api is called every 15 mins.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.apply { adapter = MainAdapter() viewModel = ViewModelProvider(this@FlagFragment)[MyViewModel::class.java] val constraints: Constraints = Constraints.Builder().apply { setRequiredNetworkType(NetworkType.CONNECTED) setRequiresBatteryNotLow(true) }.build() notificationWorkRequest = PeriodicWorkRequest.Builder( ApiCallWork::class.java, 15, TimeUnit.MINUTES, 15, TimeUnit.MINUTES ) .setConstraints(constraints) .build() WorkManager.getInstance(requireContext()).enqueueUniquePeriodicWork( "my_work", ExistingPeriodicWorkPolicy.KEEP, notificationWorkRequest ) WorkManager.getInstance(requireContext()).enqueue(notificationWorkRequest) recyclerView.adapter = adapter getObserver() val getList = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when (intent?.action) { "MyReciever" -> { val name = intent.extras?.getString("list") if (name != null) { val data = readJsonFromInternalStorage( requireContext(), name ) myModelList = parseJsonToModelList(data) Log.d("data", myModelList.toString()) viewModel.setFlagList(myModelList as List< MainFlagModel>) } } } } } requireActivity().registerReceiver(getList, IntentFilter("MyReciever")) } }

here is getObserver Function :

private fun getObserver() { viewModel.flagList.observe(requireActivity()) { if (it.isNotEmpty()) { adapter.setFlagList(it) } } }

Other functions :

private fun readJsonFromInternalStorage(context: Context, fileName: String): String { try { val fileInputStream = context.openFileInput(fileName) val stringBuilder = StringBuilder() val inputStreamReader = InputStreamReader(fileInputStream) val bufferedReader = BufferedReader(inputStreamReader) var line: String? = bufferedReader.readLine() while (line != null) { stringBuilder.append(line).append("\n") line = bufferedReader.readLine() } return stringBuilder.toString() } catch (e: Exception) { e.printStackTrace() return "" } } private fun parseJsonToModelList(jsonData: String): List< MainFlagModel> { return Gson().fromJson(jsonData, object : TypeToken< List< MainFlagModel>>() {}.type) }

Now the worker class

class ApiCallWork(private var context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { private val list = ArrayList< MainFlagModel>() @SuppressLint("RestrictedApi") override fun doWork(): Result { var result: Result = Result.retry() val countDownLatch = CountDownLatch(1) CoroutineScope(Dispatchers.IO).launch { val response = ApiCall().getService(baseUrl)?.allFlagDetails() withContext(Dispatchers.Main) { if (response != null) { result = if (response.isSuccessful) { list.addAll(response.body() as List< MainFlagModel>) Log.d("response", response.body().toString()) val intent = Intent("MyReciever") intent.putExtra("list", outputData(list)) context.sendBroadcast(intent) Result.success() } else { Log.d("fail", response.message().toString()) Result.retry() } countDownLatch.countDown() } } } try { countDownLatch.await() } catch (e: InterruptedException) { e.printStackTrace() } return result } private fun outputData(list: ArrayList< MainFlagModel>): String { val name = "response.json" val json = Gson().toJson(list) Log.d("json", json) val outputStream: FileOutputStream try { outputStream = context.openFileOutput(name, Context.MODE_PRIVATE) outputStream.write(json.toByteArray()) outputStream.close() } catch (e: Exception) { e.printStackTrace() } return name } }

You can see that the data has been succesfully set

enter image description here

And the api is being called every 15 minutes.

First time called

first time

Second time called

second time called


other answer :

To achieve your goal of periodically fetching data from an API in the background and updating the UI, you can follow a structured approach using WorkManager and LiveData. Heres how you can do it:

Create a Worker to Fetch Data: Create a Worker class that fetches data from the API. This worker will be responsible for making network requests and processing the JSON response. You can parse the JSON response into a list of WeatherData objects.

Pass Data to Repository: Once you fetch the data in the Worker, pass it to a repository class. The repository will be responsible for storing and managing the data.

Repository Updates LiveData: Expose a LiveData object from the repository that holds the fetched WeatherData. Whenever new data is fetched, update the LiveData object.

Observe LiveData in UI: In your UI (Activity or Fragment), observe the LiveData object exposed by the repository. Whenever the data changes, update the UI accordingly.

Heres a rough example of how your code structure might look:

java// WeatherWorker.java public class WeatherWorker extends Worker { @NonNull @Override public Result doWork() { // Fetch data from API List< WeatherData> weatherDataList = fetchDataFromApi(); // Pass data to repository WeatherRepository.getInstance().updateWeatherData(weatherDataList); return Result.success(); } } // WeatherRepository.java public class WeatherRepository { private MutableLiveData< List< WeatherData>> weatherDataLiveData; private static WeatherRepository instance; private WeatherRepository() { weatherDataLiveData = new MutableLiveData< >(); } public static WeatherRepository getInstance() { if (instance == null) { instance = new WeatherRepository(); } return instance; } public LiveData< List< WeatherData>> getWeatherData() { return weatherDataLiveData; } public void updateWeatherData(List< WeatherData> weatherDataList) { weatherDataLiveData.setValue(weatherDataList); } } // MainActivity.java public class MainActivity extends AppCompatActivity { private WeatherViewModel weatherViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Initialize ViewModel weatherViewModel = new ViewModelProvider(this).get(WeatherViewModel.class); // Observe LiveData weatherViewModel.getWeatherData().observe(this, weatherDataList -> { // Update UI with new data // Update RecyclerView, TextViews, etc. }); // Start periodic work startPeriodicWork(); } private void startPeriodicWork() { Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build(); PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder( WeatherWorker.class, PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS) .setConstraints(constraints) .build(); WorkManager.getInstance(this).enqueue(periodicWorkRequest); } } // WeatherViewModel.java public class WeatherViewModel extends AndroidViewModel { private WeatherRepository weatherRepository; private LiveData< List< WeatherData>> weatherData; public WeatherViewModel(@NonNull Application application) { super(application); weatherRepository = WeatherRepository.getInstance(); weatherData = weatherRepository.getWeatherData(); } public LiveData< List< WeatherData>> getWeatherData() { return weatherData; } }

In this setup, the WeatherWorker fetches data from the API and updates the WeatherRepository. The WeatherRepository holds the fetched data and exposes it through a LiveData object. The MainActivity observes this LiveData object and updates the UI accordingly. The periodic work is started in the MainActivity, ensuring that data is fetched periodically in the background.