Фрагмент LogCat:
Код: Выделить всё
Scheduling work ID faacd85c-18fb-43a0-950c-55473ed2de3aJob ID 42
Starting work for faacd85c-18fb-43a0-950c-55473ed2de3a
Processor: processing WorkGenerationalId(workSpecId=faacd85c-18fb-43a0-950c-55473ed2de3a, generation=0)
Compat change id reported: 263076149; UID 10218; state: ENABLED
Could not instantiate com.amel.faerntourism.worker.DailyInterestingPlaceNotificationWorker
java.lang.NoSuchMethodException: com.amel.faerntourism.worker.DailyInterestingPlaceNotificationWorker. [class android.content.Context, class androidx.work.WorkerParameters]
at java.lang.Class.getConstructor0(Class.java:3395)
at java.lang.Class.getDeclaredConstructor(Class.java:3077)
at androidx.work.WorkerFactory.createWorkerWithDefaultFallback$fallbackToReflection(WorkerFactory.kt:87)
at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.kt:96)
at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.kt:234)
at androidx.work.impl.WorkerWrapper.access$runWorker(WorkerWrapper.kt:67)
at androidx.work.impl.WorkerWrapper$launch$1$resolution$1.invokeSuspend(WorkerWrapper.kt:98)
at androidx.work.impl.WorkerWrapper$launch$1$resolution$1.invoke(Unknown Source:8)
at androidx.work.impl.WorkerWrapper$launch$1$resolution$1.invoke(Unknown Source:4)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:42)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:164)
at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
at androidx.work.impl.WorkerWrapper$launch$1.invokeSuspend(WorkerWrapper.kt:98)
at androidx.work.impl.WorkerWrapper$launch$1.invoke(Unknown Source:8)
at androidx.work.impl.WorkerWrapper$launch$1.invoke(Unknown Source:4)
at androidx.work.ListenableFutureKt$launchFuture$1$2.invokeSuspend(ListenableFuture.kt:42)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:101)
at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at java.lang.Thread.run(Thread.java:1012)
Could not create Worker com.amel.faerntourism.worker.DailyInterestingPlaceNotificationWorker
androidx.work.impl.background.systemalarm.RescheduleReceiver enabled
androidx.work.impl.background.systemalarm.RescheduleReceiver disabled
Вот несколько фрагментов: < /p>
Работник: < /p>
Код: Выделить всё
package com.amel.faerntourism.worker
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.amel.faerntourism.data.FireStoreRepository
import com.amel.faerntourism.worker.makeInterestingPlaceNotification
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
@HiltWorker
class DailyInterestingPlaceNotificationWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
private val fireStoreRepository: FireStoreRepository
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val placesResult = fireStoreRepository.getPlaces()
placesResult.onSuccess { places ->
if (places.isNotEmpty()) {
val randomPlace = places.random()
makeInterestingPlaceNotification(randomPlace, applicationContext)
return Result.success()
} else {
return Result.failure()
}
}.onFailure {
return Result.failure()
}
return Result.failure()
}
}
Код: Выделить всё
package com.amel.faerntourism.worker
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.amel.faerntourism.CHANNEL_ID
import com.amel.faerntourism.NOTIFICATION_ID
import com.amel.faerntourism.NOTIFICATION_TITLE
import com.amel.faerntourism.R
import com.amel.faerntourism.VERBOSE_NOTIFICATION_CHANNEL_DESCRIPTION
import com.amel.faerntourism.VERBOSE_NOTIFICATION_CHANNEL_NAME
import com.amel.faerntourism.data.model.Place
@SuppressLint("MissingPermission")
fun makeInterestingPlaceNotification(
place: Place,
context: Context
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createNotificationChannel(context)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.landscape)
.setContentTitle(NOTIFICATION_TITLE)
.setContentText(place.name)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, builder)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(context: Context) {
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(
CHANNEL_ID,
VERBOSE_NOTIFICATION_CHANNEL_NAME,
importance
)
channel.description = VERBOSE_NOTIFICATION_CHANNEL_DESCRIPTION
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
notificationManager?.createNotificationChannel(channel)
}
Код: Выделить всё
package com.amel.faerntourism.data
import com.amel.faerntourism.data.model.Article
import com.amel.faerntourism.data.model.Place
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.toObject
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
interface FireStoreRepository {
suspend fun getPlaces(): Result
suspend fun getArticles(): Result
suspend fun getPlace(id: String): Result
suspend fun getArticle(id: String): Result
}
class FireStoreRepositoryImpl @Inject constructor(
private val fireStore: FirebaseFirestore
) : FireStoreRepository {
override suspend fun getPlaces(): Result =
try {
val snapshot = fireStore.collection(PLACES_COLLECTION).get().await()
val places = snapshot.documents.map { document ->
document.toObject()?.copy(id = document.id) ?: Place()
}
Result.success(places)
} catch (e: Exception) {
Result.failure(e)
}
override suspend fun getArticles(): Result =
try {
val snapshot = fireStore.collection(ARTICLES_COLLECTION).get().await()
val articles = snapshot.documents.map { document ->
document.toObject()?.copy(id = document.id) ?: Article()
}
Result.success(articles)
} catch (e: Exception) {
Result.failure(e)
}
override suspend fun getPlace(id: String): Result =
try {
val docSnapshot = fireStore.collection(PLACES_COLLECTION)
.document(id)
.get()
.await()
if (docSnapshot.exists()) {
Result.success(
docSnapshot.toObject()?.copy(id = docSnapshot.id) ?: Place()
)
} else {
Result.failure(NoSuchElementException("No place found with id $id"))
}
} catch (e: Exception) {
Result.failure(e)
}
override suspend fun getArticle(id: String): Result =
try {
val docSnapshot = fireStore.collection(ARTICLES_COLLECTION)
.document(id)
.get()
.await()
if (docSnapshot.exists()) {
Result.success(
docSnapshot.toObject()?.copy(id = docSnapshot.id) ?: Article()
)
} else {
Result.failure(NoSuchElementException("No article found with id $id"))
}
} catch (e: Exception) {
Result.failure(e)
}
companion object {
const val ARTICLES_COLLECTION = "articles"
const val PLACES_COLLECTION = "places"
}
}
Код: Выделить всё
package com.amel.faerntourism.di
import com.amel.faerntourism.data.FireStoreRepository
import com.amel.faerntourism.data.FireStoreRepositoryImpl
import com.google.firebase.Firebase
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.firestore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object FireStoreModule {
@Singleton
@Provides
fun provideFireStore(): FirebaseFirestore = Firebase.firestore
@Singleton
@Provides
fun providesFireStoreRepository(impl: FireStoreRepositoryImpl): FireStoreRepository = impl
}
< /code>
Основное действие: < /p>
package com.amel.faerntourism
import DailyInterestingPlaceNotificationWorker
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.amel.faerntourism.ui.AuthViewModel
import com.amel.faerntourism.ui.screens.detailed.ArticleScreen
import com.amel.faerntourism.ui.screens.detailed.PlaceScreen
import com.amel.faerntourism.ui.screens.general.AccountScreen
import com.amel.faerntourism.ui.screens.general.ArticlesScreen
import com.amel.faerntourism.ui.screens.general.HomeScreen
import com.amel.faerntourism.ui.screens.general.ToursScreen
import com.amel.faerntourism.ui.theme.FaernTourismTheme
import dagger.hilt.android.AndroidEntryPoint
import java.util.concurrent.TimeUnit
@AndroidEntryPoint
class FaernActivity : ComponentActivity() {
private val authViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val periodicRequest = PeriodicWorkRequestBuilder(
12, TimeUnit.HOURS,
).build()
WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork(
DAILY_WORK_NAME,
ExistingPeriodicWorkPolicy.KEEP,
periodicRequest
)
setContent {
FaernTourismTheme {
Surface(
modifier = Modifier.fillMaxSize()
) {
val navController = rememberNavController()
FaernNavHost(navController, authViewModel)
}
}
}
}
}
Код: Выделить всё
package com.amel.faerntourism
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class FaernApplication : Application()
< /code>
androidmanifest: < /pbr />
< /code>
Некоторые зависимости от рукояти: < /p>
implementation(libs.hilt.android)
kapt(libs.hilt.android.compiler)
implementation(libs.androidx.hilt.work)
kapt(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose)
...
[versions]
hiltAndroid = "2.51.1"
hiltNavigationCompose = "1.2.0"
androidxHiltWork = "1.2.0"
androidxHiltCompiler = "1.2.0"
...
[libraries]
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltAndroid" }
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hiltAndroid" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-hilt-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "androidxHiltWork" }
androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "androidxHiltCompiler" }
@HiltAndroidApp
class FaernApplication : Application(), Configuration.Provider {
@Inject
lateinit var workerFactory: HiltWorkerFactory
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
< /code>
Но это тоже не помогло. < /p>
p.s. Уведомления включены на эмулятор.>
Подробнее здесь: https://stackoverflow.com/questions/793 ... repository