Kotlin База данных ROOM - привязка UI - PullRequest
0 голосов
/ 02 августа 2020

Итак, я пытаюсь сделать следующее в своем приложении android:

  1. Получить JSON из REST API, проанализировать его и вложенные списки
  2. Store значения, полученные из этого JSON в локальной базе данных ROOM
  3. Получить значения из базы данных и отобразить их на моем фрагменте с использованием архитектуры MVVM

# 1 был успешным. Я могу использовать Retrofit2 для извлечения, анализа и отображения данных

# 2 Я не уверен. ROOM явно недружелюбен к сложным типам данных. Я продолжал получать ошибку cannot find setter method для каждого val, который я пытался создать, несмотря на использование конструктора, инициализацию значений и установку @Ignore для списков и объектов. Итак, все val в классе данных были преобразованы в var, и проект, наконец, построен. Но я не знаю, вызовет ли это огромные проблемы. Я не знаю, была ли база данных успешно создана, но она генерирует точную схему (так что да?)

# 3, я думаю, что реализовал ее, следуя руководству, но теперь созданный мной метод, который связывает пользовательский интерфейс и загружает RecyclerView, ничего не возвращает. В остальном программа выполняется без ошибок или исключений.

Я думаю, что проблема может быть в разделе FRAGMENT при выполнении bindsui внутри onCreateView . Моя IDE дает подсказку о том, что возвращаемое значение не используется, и я не знаю, почему

Я также не знаю, напутал ли я что-то, потому что я вижу в журнале Background young concurrent copying GC freed 2775(325KB) AllocSpace objects, 0(0B) LOS objects, 15% free, 1988KB/2355KB, paused 731us total 437.316ms, который очевидно означает, что мое приложение забирает много памяти (хотя я даже не загружаю и не кэширую столько данных).

См. ниже:

DATA CLASS / ENTITY


import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName

const val RESTAURANT_ID = 0

@Entity(tableName = "nearby_restaurant")

data class NearbyRestaurant(
    @SerializedName("nickname")
    var nickname: String,
    @SerializedName("address")
    var address: String,
    @SerializedName("app")
    var app: String,
    @SerializedName("catchUpClose")
    @field:Embedded(prefix = "catchUpClose_")
    var catchUpClose: CatchUpClose?,
    @SerializedName("cateringAdvanceTime")
    var cateringAdvanceTime: Int,
    @SerializedName("city")
    var city: String,
    @SerializedName("createdAt")
    var createdAt: String,
    @SerializedName("curbside")
    var curbside: Boolean,
    @SerializedName("delivery")
    var delivery: Boolean,
    @SerializedName("deliveryByTap")
    var deliveryByTap: Boolean,
    @SerializedName("deliveryFee")
    var deliveryFee: Int,
    @SerializedName("deliveryRadius")
    var deliveryRadius: Int,
    @SerializedName("description")
    var description: String,
    @SerializedName("dinein")
    var dinein: Boolean,
    @SerializedName("distanceInMeters")
    var distanceInMeters: Double,
    @SerializedName("dollars")
    var dollars: Int,
    @SerializedName("flags")
    @field:Embedded(prefix = "flags_")
    var flags: Flags?,
    @SerializedName("holidaySchedule")
    @Ignore
    var holidaySchedule: List<String>?,
    @SerializedName("isNew")
    var Newis: Boolean,
    @SerializedName("latitude")
    var latitude: Double,
    @SerializedName("logo")
    var logo: String,
    @SerializedName("longitude")
    var longitude: Double,
    @SerializedName("minimumDelivery")
    var minimumDelivery: Int,
    @SerializedName("name")
    var name: String,
    @SerializedName("offline")
    var offline: Boolean,
    @SerializedName("phone")
    var phone: String,
    @SerializedName("flags")
    @field:Embedded(prefix = "schedule_")
    var schedule: Schedule?,
    @SerializedName("state")
    var state: String,
    @SerializedName("stopOrders")
    var stopOrders: Int,
    @SerializedName("takeout")
    var takeout: Boolean,
    @SerializedName("updatedAt")
    var updatedAt: String,
    @SerializedName("vacation")
    @Ignore
    var vacation: List<String>?,
    @SerializedName("working")
    @Ignore
    var working: List<Int>?,
    @SerializedName("zip")
    var zip: String
) {
    @field:PrimaryKey(autoGenerate = false)
    var restaurant_id: Int = RESTAURANT_ID
    @JvmOverloads
    constructor() : this("", "", "", null, 0, "", "", false, false, false,
        0, 0, "", false, 0.0, 0, null, null, false, 0.0, "", 0.0, 0,
        "", false, "", null,
        "", 0, false, "", null, null, "" )

}

DAO

@Dao
interface RestaurantsDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun upsert(restaurantEntry: List<NearbyRestaurant>)

    @Query("SELECT * FROM nearby_restaurant WHERE restaurant_id = $RESTAURANT_ID")
    fun getNearbyDB(): LiveData<NearbyRestaurant>


}

БАЗА ДАННЫХ

@Database(
    entities = [NearbyRestaurant::class],
    version = 2
)
abstract class RestaurantsDB : RoomDatabase() {

    abstract fun restaurantsDao(): RestaurantsDao

    companion object {
        @Volatile private var instance: RestaurantsDB? = null
        private val LOCK = Any()

        operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
            instance ?: buildDB(context).also { instance = it}
        }

        private fun buildDB(context: Context) =
            Room.databaseBuilder(context.applicationContext,
                RestaurantsDB::class.java, "restaurants.db")
                .build()
    }

}

ФРАГМЕНТ

// custom scope
class RestaurantsFragment : ScopedFragment(), DIAware {

    override val di: DI by closestDI()
    private val viewModelFactory: RestaurantsViewModelFactory by instance1()
    private lateinit var viewModel: RestaurantsViewModel

    val TAG = "APP-DEBUG"


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val root =  inflater.inflate(R.layout.restaurants_fragment, container, false)
        return root

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProvider(this, viewModelFactory)
            .get(RestaurantsViewModel::class.java)
        Log.e(TAG, "about to bind ui ")
        bindUI()
    }

    fun load_recycler_view(nearbyResponses: List<NearbyRestaurant>) {
        Log.e(TAG, "recyclerview invoked")
        recycle_restaurants.apply {
            layoutManager = LinearLayoutManager(this.context)
            adapter = RestaurantAdapter(nearbyResponses)
            setHasFixedSize(true)

        }
    }

    private fun bindUI(): () -> Job =  {launch {
        val nearbyrestaurant = viewModel.nearby.await()
        nearbyrestaurant.observe(viewLifecycleOwner, Observer {
            if (it == null) return@Observer

            load_recycler_view(listOf(it))
        })
        }
    }

}

ПРОСМОТР МОДЕЛИ

class RestaurantsViewModel(private val restaurantsRepository: RestaurantsRepository) : ViewModel() {
    val nearby by lazyDeferred { restaurantsRepository.getNearbyRestaurants() }

    }
---------------------------------------------------------------------------------------------

// lazyDeferred (custom)
fun <T> lazyDeferred(block: suspend CoroutineScope.() -> T): Lazy<Deferred<T>> {
    return lazy {
        GlobalScope.async(start = CoroutineStart.LAZY) {
            block.invoke(this)
        }
    }
}

ПРОСМОТР МОДЕЛИ

class RestaurantsViewModelFactory(
    private val restaurantsRepository: RestaurantsRepository
) : ViewModelProvider.NewInstanceFactory(){
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return RestaurantsViewModel(restaurantsRepository) as T
    }
}

РЕПОЗИТОРИЙ

class RestaurantsRepositoryImpl(
    private val restaurantsDao: RestaurantsDao,
    private val restaurantNetworkDataSource: RestaurantNetworkDataSource
) : RestaurantsRepository {
    init {
        restaurantNetworkDataSource.downloadedRestaurants.observeForever { newRestaurant ->
            //persist
            persistFetchedRestaurant(newRestaurant)

        }
    }
    override suspend fun getNearbyRestaurants(): LiveData<NearbyRestaurant> {
        return withContext(Dispatchers.IO) {
            initRestaurantData()
            return@withContext restaurantsDao.getNearbyDB()
        }
    }

    private fun persistFetchedRestaurant(fetchedRestaurant: NearbyResponse) {
        GlobalScope.launch(Dispatchers.IO) {
            restaurantsDao.upsert(fetchedRestaurant.nearbyRestaurant)
        }
    }

    private suspend fun initRestaurantData() {
        if (isFetchRestaurantNeeded(ZonedDateTime.now().minusHours(1)))
            fetchRestaurants()
    }

    private suspend fun fetchRestaurants() {
        restaurantNetworkDataSource.fetchRestaurants(
            3,
            40.7539,
            74.40816,
        3
        )
    }

    private fun isFetchRestaurantNeeded(lastFetchTime: ZonedDateTime): Boolean {
        val thirtyMinutesAgo = ZonedDateTime.now()
        return lastFetchTime.isBefore(thirtyMinutesAgo)
    }

}

ЗАВИСИМОСТЬ ВПРЫСКА


import org.kodein.di.*
import org.kodein.di.android.x.androidXModule

class MyApp : Application(), DIAware {

     override val di: DI
             get() =  DI.lazy {
                    import(androidXModule(this@MyApp))
                    bind() from singleton { RestaurantsDB(instance()) } // this is my database
                    bind() from singleton { instance<RestaurantsDB>().restaurantsDao() }
                    bind<ConnectivityInterceptor>() with singleton {ConnectivityInterceptorImpl(instance())} // ConnectivityInterceptor checks for network validity and prevents crashing if no network connection
                    bind() from singleton { tapAPI(instance()) } // this interface is the structure of my retrofit caller
                    bind<RestaurantNetworkDataSource>() with singleton { RestaurantNetworkDataSourceImpl(instance()) } // this fetches and downloads the data
                    bind<RestaurantsRepository>() with singleton { RestaurantsRepositoryImpl(instance(), instance()) } //connects to repository
                    bind() from provider { RestaurantsViewModelFactory(instance()) } //push to fragment

     }


}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...