I have a set of big tasks which I want to execute in background:
Room
For this reason I created unique chain of Worker
's with the same tag
.
class GtfsStaticManager() {
private val workerManager = WorkManager.getInstance()
override fun load() {
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val inputData = GtfsStaticLoadDataWorker.inputData(staticUrl, cacheDir)
// 1. Loading data
val downloadWorkRequest = OneTimeWorkRequest.Builder(GtfsStaticLoadDataWorker::class.java)
.addTag("GTFS")
.setConstraints(constraints)
.setInputData(inputData)
.build()
// 2. List of Workers to parse and store data to the Room
val parseWorkers = GtfsFile.values().map {
OneTimeWorkRequest.Builder(GtfsStaticParseFileWorker::class.java)
.setInputData(GtfsStaticParseFileWorker.inputData(it.file, cacheDir + File.separator + "feed"))
.addTag("GTFS")
.build()
}
workerManager
.beginUniqueWork("GTFS", ExistingWorkPolicy.KEEP, downloadWorkRequest)
.then(parseWorkers)
.enqueue()
}
}
Everything works fine except one thing: one of the files has 4 million records and it takes around 10-15 minutes to finish it. After some time I noticed that it was enqueued again BUT the first job was still running, so as result I have 2 huge jobs running in background and of course my data was duplicated.
I followed codelabs tutorial, did I miss something?
Below is my Worker
with parsing logic:
class GtfsStaticParseFileWorker(
context: Context,
workerParameters: WorkerParameters
) : Worker(context, workerParameters) {
private val fileName: String get() = inputData.getString(FILE_NAME) ?: ""
private val cacheDir: String get() = inputData.getString(UNZIP_FOLDER) ?: ""
companion object {
private const val FILE_NAME = "FILE_NAME"
private const val UNZIP_FOLDER = "UNZIP_FOLDER"
fun inputData(fileName: String, cacheDir: String) = Data
.Builder()
.putString(FILE_NAME, fileName)
.putString(UNZIP_FOLDER, cacheDir)
.build()
}
override fun doWork(): Result {
val db = LvivTransportTrackerDataBase.getUpdateInstance(applicationContext)
val agencyRepository = AgencyRepository(db.agencyDao())
val calendarRepository = CalendarRepository(db.calendarDao())
val calendarDateRepository = CalendarDateRepository(db.calendarDateDao())
val routeRepository = RouteRepository(db.routeDao())
val stopTimeRepository = StopTimeRepository(db.stopTimeDao())
val stopRepository = StopRepository(db.stopDao())
val tripRepository = TripRepository(db.tripDao())
val file = File(cacheDir + File.separator + fileName)
val fileType = GtfsFile.from(fileName) ?: return Result.failure()
when (fileType) {
GtfsFile.Agency -> agencyRepository.deleteAll()
GtfsFile.CalendarDates -> calendarDateRepository.deleteAll()
GtfsFile.Calendar -> calendarRepository.deleteAll()
GtfsFile.Routes -> routeRepository.deleteAll()
GtfsFile.StopTimes -> stopTimeRepository.deleteAll()
GtfsFile.Stops -> stopRepository.deleteAll()
GtfsFile.Trips -> tripRepository.deleteAll()
}
FileInputStream(file).use { fileInputStream ->
InputStreamReader(fileInputStream).use inputStreamReader@{ inputStreamReader ->
val bufferedReader = BufferedReader(inputStreamReader)
val headers = bufferedReader.readLine()?.split(',') ?: return@inputStreamReader
var line: String? = bufferedReader.readLine()
while (line != null) {
val mapLine = headers.zip(line.split(',')).toMap()
Log.d("GtfsStaticParse", "$fileType: $line")
when (fileType) {
GtfsFile.Agency -> agencyRepository.create(AgencyEntity(mapLine))
GtfsFile.CalendarDates -> calendarDateRepository.create(CalendarDateEntity(mapLine))
GtfsFile.Calendar -> calendarRepository.create(CalendarEntity(mapLine))
GtfsFile.Routes -> routeRepository.create(RouteEntity(mapLine))
GtfsFile.StopTimes -> stopTimeRepository.create(StopTimeEntity(mapLine))
GtfsFile.Stops -> stopRepository.create(StopEntity(mapLine))
GtfsFile.Trips -> tripRepository.create(TripEntity(mapLine))
}
line = bufferedReader.readLine()
}
}
}
return Result.success()
}
}
P.S. my dependency is implementation "android.arch.work:work-runtime:1.0.0"
Worker classes, in WorkManager, have an execution limit of 10 minutes.
From WorkManager guide on how to handle cancellation:
The system instructed your app to stop your work for some reason. This can happen if you exceed the execution deadline of 10 minutes. The work is scheduled for retry at a later time.
In your case, you're not handling the stopping of the work but WorkManager will ignore any result because it marked the job an "cancelled" and it will execute it again when possible.
This can result in the double execution that you're experiencing.
It's difficult to suggest an alternative approach without knowing more about the goal you want achieve however, as a general rule, WorkManager is intended for deferrable tasks that need a guaranteed execution.
WorkManager documentation has been expanded after the 1.0 release and you can find more information there.