dependencies { def room_version = "2.5.2" // Ellenőrizd a legújabb verziót! implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" // Kotlin Coroutines támogatás, ha szükséges implementation "androidx.room:room-ktx:$room_version" }
@Entity data class User( @PrimaryKey(autoGenerate = true) val id: Int = 0, val name: String, val age: Int )
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertUser(user: User) @Query("SELECT * FROM user WHERE id = :userId") suspend fun getUserById(userId: Int): User? }
@Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "appDatabase" ).build() val userDao = db.userDao()
@Entity @Parcelize data class User( @PrimaryKey val userId: String = "", @Embedded(prefix = "personalInfo_") val personalInfo: PersonalInfo?, val dataSyncState: DataSyncState, val lastSyncDate: Date? = null, ) : Parcelable @Parcelize data class PersonalInfo( val firstName: String, val lastName: String, val email: String, val phoneNumber: String, ) : Parcelable
class DateTypeConverter { @TypeConverter fun fromDate(value: Long?): Date? { return value?.let { Date(it) } } @TypeConverter fun toDate(date: Date?): Long? { return date?.time } }
class DataSyncStateTypeConverter { @TypeConverter fun fromStatus(enum: DataSyncState?): String? { return enum?.name } @TypeConverter fun toStatus(value: String?): DataSyncState? { return value?.let { enumValueOf<DataSyncState>(it) } } }
@Database( entities = [ User::class, Task::class, ], version = 1, exportSchema = false ) @TypeConverters( DateTypeConverter::class, DataSyncStateTypeConverter::class, ) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun taskDao(): TaskDao }
class WeatherListTypeConverter { private val gson = GsonBuilder() .create() @TypeConverter fun listToString(list: List<DemoObject>): String { return gson.toJson(list) } @TypeConverter fun stringToList(data: String): List<DemoObject> { val type = object : TypeToken<List<DemoObject>>() {}.type return try { gson.fromJson(data, type) } catch (e: JsonParseException) { listOf() } } }
@Entity( foreignKeys = [ForeignKey( entity = User::class, parentColumns = ["userId"], childColumns = ["taskId"], onDelete = ForeignKey.CASCADE, )] ) data class Task( @PrimaryKey val taskId: String = "", val description: String, val userId: String, // Foreign key )
data class UserWithTasks( @Embedded val user: User, @Relation( parentColumn = "userId", entityColumn = "taskId" ) val tasks: List<Task>, )
@Dao interface UserDao { @Query("select * from User") suspend fun getUsers(): List<User> @Query("select * from User where userId = :id") suspend fun getUser(id: String): User? @Query("SELECT * FROM User where userId = :id") suspend fun getUserWithTasks(id: String): UserWithTasks @Query("select * from User") fun getUsersFlow(): Flow<List<User>> @Query("select * from User where userId = :id") fun getUserFlow(id: String): Flow<User?> @Query("SELECT * FROM User where userId = :id") fun getUserWithTasksFlow(id: String): Flow<UserWithTasks> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertUser(user: User) @Update(onConflict = OnConflictStrategy.REPLACE) suspend fun updateUser(user: User) @Delete suspend fun deleteUser(user: User) @Query("delete from User") suspend fun deleteAll() }
@Dao interface TaskDao { @Query("select * from Task where taskId = :id") suspend fun getTask(id: String): Task? @Query("select * from Task") suspend fun getTasks(): List<Task> @Query("select * from Task where taskId = :id") fun getTaskFlow(id: String): Flow<Task?> @Query("select * from Task") fun getTasksFlow(): Flow<List<Task>> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertTask(task: Task) @Update(onConflict = OnConflictStrategy.REPLACE) suspend fun updateTask(task: Task) @Delete suspend fun deleteTask(task: Task) @Query("delete from Task") suspend fun deleteAll() }
@Module @InstallIn(SingletonComponent::class) class DatabaseModule { @Singleton @Provides fun provideDataBase( @ApplicationContext context: Context, configuration: Configuration, ): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, configuration.appDatabaseName) .build() } @Singleton @Provides fun provideUserDao(database: AppDatabase): UserDao { return database.userDao() } @Singleton @Provides fun provideTaskDao(database: AppDatabase): TaskDao { return database.taskDao() } }
interface UserStorage { suspend fun getUser(): User? suspend fun saveUser(user: User) } @Singleton class UserStorageImpl @Inject constructor( private val userDao: UserDao, ): UserStorage { override suspend fun getUser() = userDao.getUser() override suspend fun saveUser(user: User) { userDao.insertUser(user) } }
@Module @InstallIn(SingletonComponent::class) abstract class StorageModule { @Binds abstract fun provideUserStorage(userStorage: UserStorageImpl): UserStorage @Binds abstract fun provideTaskStorage(userStorage: TaskStorageImpl): TaskStorage }
class InternetConnectivityListener(private val context: Context) { val networkState = MutableStateFlow(NetworkState.UNKNOWN) private val connectivityManager: ConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager private val networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { super.onAvailable(network) networkState.value = NetworkState.CONNECTED } override fun onLost(network: Network) { super.onLost(network) networkState.value = NetworkState.NOT_CONNECTED } } init { registerNetworkCallback() } private fun registerNetworkCallback() { val networkRequest = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build() connectivityManager.registerNetworkCallback(networkRequest, networkCallback) } fun unregisterNetworkCallback() { connectivityManager.unregisterNetworkCallback(networkCallback) } }
interface GetNetworkStateUseCase { fun invoke(): Flow<NetworkState> } class GetNetworkStateUseCaseImpl @Inject constructor( @ApplicationContext private val applicationContext: Context, ) : GetNetworkStateUseCase { private val internetConnectivityListener by lazy { InternetConnectivityListener(applicationContext) } override fun invoke(): Flow<NetworkState> { return internetConnectivityListener.networkState } }
interface HasUnSyncedDataUseCase { fun invoke(): Flow<Boolean> } class HasUnSyncedDataUseCaseImpl @Inject constructor( private val userStorage: UserStorage, private val taskStorage: TaskStorage, ) : HasUnSyncedDataUseCase { override fun invoke(): Flow<Boolean> { val flows = listOf( userStorage.getUsersFlow(DataSyncState.IN_LOCAL_DATABASE), taskStorage.getTasksFlow(DataSyncState.IN_LOCAL_DATABASE), ) return combine(flows) { val hasUnSyncedUsers = it[0].isNotEmpty() val hasUnSyncedTasks = it[1].isNotEmpty() hasUnSyncedVendors || hasUnSyncedModifiedVendors } } }
enum class DataSyncState { SYNCED_TO_CLOUD, IN_LOCAL_DATABASE, UNDEFINED, }
interface GetCurrentSyncStateUseCase { fun invoke(): Flow<SyncStateModel> } class GetCurrentSyncStateUseCaseImpl @Inject constructor( private val hasUnSyncedDataUseCase: HasUnSyncedDataUseCase, private val getNetworkStateUseCase: GetNetworkStateUseCase, ) : GetCurrentSyncStateUseCase { override fun invoke(): Flow<SyncStateModel> { return combine(getNetworkStateUseCase.invoke(), hasUnSyncedDataUseCase.invoke()) { networkState, hasUnSyncedData -> when (networkState) { NetworkState.UNKNOWN, NetworkState.NOT_CONNECTED -> { SyncStateModel( state = SyncState.NO_INTERNET, ) } NetworkState.CONNECTED -> { if (hasUnSyncedData) { SyncStateModel( state = SyncState.LAST_UPDATE, lastUpdateTime = "Last update", ) } else { SyncStateModel( state = SyncState.UP_TO_DATE, ) } } } } } } enum class SyncState { UP_TO_DATE, NO_INTERNET, LAST_UPDATE; } data class SyncStateModel( val state: SyncState, val lastUpdateTime: String? = null )
@Query("select * from Batch where dataSyncState = :syncState") suspend fun getUsers(syncState: DataSyncState): List<User>
interface UploadUnSyncedDataUseCase { suspend fun invoke(): Job } class UploadUnSyncedDataUseCaseImpl @Inject constructor( private val userStorage: UserStorage, private val taskStorage: TaskStorage, private val uploadUserUseCase: UploadUserUseCase, private val uploadTaskUseCase: UploadTaskUseCase, private val updateLastSyncDateUseCase: UpdateLastSyncDateUseCase, ) : UploadUnSyncedDataUseCase { override suspend fun invoke() = CoroutineScope(Dispatchers.IO + SupervisorJob()).launch { // Sync users // Find all un synced user val unSyncedUsers = userStorage.getAllUser(DataSyncState.IN_LOCAL_DATABASE) // Upload all un synced user val userSyncJobs = unSyncedUsers.map { user -> async { uploadUserUseCase.invoke(user) } } val userSyncResults = mutableListOf<Result<UserDTO>>() userSyncResults.addAll(userSyncJobs.awaitAll()) // Sync tasks // Find all un synced task val unSyncedTasks = taskStorage.getAllTask(DataSyncState.IN_LOCAL_DATABASE) // Upload all un synced task val taskSyncJobs = unSyncedTasks.map { task -> async { uploadTaskUseCase.invoke(task) } } val taskSyncResults = mutableListOf<Result<TaskDTO>>() taskSyncResults.addAll(taskSyncJobs.awaitAll()) updateLastSyncDateUseCase.invoke() } }