I have just started working with Kotlin. I have successfully got data from an API using Retrofit. I have to insert that data in SQLite. But I am not able to get a particular data from the response.
Here is my code:
apiInterface.enqueue( object : Callback<List<Movie>> {
override fun onResponse(call: Call<List<Movie>>?, response: Response<List<Movie>>?) {
if(response?.body() != null)
recyclerAdapter.setMovieListItems(response.body()!!)
response?.let {
for (i:Int in response.body()!!) {
recyclerAdapter.setMovieListItems(response.body()!!)
val myMovieList = response.body()
val myMovie = myMovieList!!.get(i)
var movie = MovieDatabase(myMovie.title, myMovie.image)
db.insertMovieData(movie)
}
}
}
override fun onFailure(call: Call<List<Movie>>?, t: Throwable?) {
}
})
}
Here is my insert method:
fun insertMovieData(movie: MovieDatabase) {
val db = this.writableDatabase
var cv = ContentValues()
cv.put(COL_FIRST_NAME, movie.name)
cv.put(COL_LAST_NAME, movie.image)
var result = db.insert(TABLE_NAME_MOVIE, null, cv)
if (result == -1.toLong())
Toast.makeText(context, "Failed", Toast.LENGTH_SHORT).show()
else
Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show()
}
If you've successfully, got back List<Movie> from the response body, just put the response into a List<Movie> myList.
List<Movie> myMovieList = response.body();
Then loop over it to get the values you need.
Movie myMovie = myList.get(0); //To get first Movie in list and so on
Then as per your Movie class use the getter methods to fetch further details of the movie; For example:
String imgURL = myMovie.getImage();
String movieName = myMovie.getTitle();
Build a SQLite DB from Room Persistence Library (it's simpler and easier than directly using SQLite database) and add the movie information there. Read - Save data in a local database using Room. Or, continue with your SQLite database, and call the respective insert method and query you've built with the data you got in imgURL and movieName.
You can also have a special method in your database handler class which could take entire myMovieList in one go and iterate over it inserting the values into database one by one.
My code examples are in Java, but, you should be able to write your Kotlin equivalent ones.
Here's the official documentation on Room Persistence Library.
Related
I built an application in Kotlin using MVVM. I fetched the API response from the server successfully. Now I want to insert API's parsing data into RoomDB.
API response included both JSON Object & Array. I want to insert specific data from API to DB. Please help me with how I can make an entity class and set parsing data to the class data members with/without loops and insert it into RoomDB by making a single list of the whole class.
Please provide tutorial links or any kind of material links instead of the Android Developers Guide. Thanks a lot!
In API Response we have many data but actually, we don't need that all that so basically we to create one data class that is only constant the specific that actually, we need. and that all operation is performed in a repository and we manage it.
entity class that only contains essential data
#Entity(tableName = "wallpaper")
data class WallpaperDataClass (
#PrimaryKey(autoGenerate = true)
val note_id:Int=0,
val photoId: Int,
val photos_url: String,
val photographer_name: String,
val photographer_url: String,
val src:String
)
Fill the data in model
if (NetworkUtils.isOnline(applicationContext)) {
/**
* Online
* if Your net is online then call api
*/
try {
val result: Response<PhotoModel> =
wallpaperInterface.getWallpaper(authorization, page, per_page)
if (result.body() != null) {
val photos = mutableListOf<WallpaperDataClass>()
result.body()!!.photos.forEach {
// in blows line we set data in modal
val wallpaperDataClass = WallpaperDataClass(
photoId = it.id,
photos_url = it.url,
photographer_name = it.photographer,
photographer_url = it.photographerUrl,
src = it.src.portrait
)
photos.add(wallpaperDataClass)
if (!offlineDatabase.getDao().exists(it.id)){
offlineDatabase.getDao().insertWallpaper(wallpaperDataClass)
}
mutableLiveData.postValue(ErrorHandling.Success(photos))
}
} else {
Log.d("WallpaperResponse", "getWallpaper: ${result.message()}")
}
} catch (e: Exception) {
mutableLiveData.postValue(ErrorHandling.Faild(e.localizedMessage!!.toString()))
}
} else {
/**
* Offline
*if Your net is offline then fetch from db
*/
try {
val wallpaper = offlineDatabase.getDao().getOfflineWallpaper()
mutableLiveData.postValue(ErrorHandling.Success(wallpaper))
} catch (e: Exception) {
mutableLiveData.postValue(ErrorHandling.Faild(e.localizedMessage!!.toString()))
}
}
}
}
Video Tutorial
I'm a beginner in asynchronous operations. My goal is to check whether data with specific date already exist in Room database or not, if not then start download from server and insert it to Room. But my following codes execute TODO: Processing Data in Fragment twice coz the coroutine re-execute it when the TODO: download insert new data finished
Here my codes:
birdDAO.kt
#Query("SELECT * FROM birds_table WHERE birdDate =:rDate ORDER BY birdId")
fun getBirdBySingleDate(rDate: Int): LiveData<List<Bird>>
birdRepository.kt
fun getBirdBySingleDate(rDate: Int) = birdDao.getBirdBySingleDate(rDate)
birdViewModel.kt
fun getBirdByDate(rDate: Int) = birdRepository.getBirdBySingleDate(rDate)
Fragment.kt
private fun loadBirdData(jDate: Int) {
val listBirdByDate = birdViewModel
.getBirdByDate(jDate)
.observe(viewLifecycleOwner){ birds ->
val size = birds.size
if(size > 0) {
//TODO Processing Data
}
else
{
//TODO: download n insert new data
}
}
}
The question is how to write the codes that only execute one-shot Room query? I tried to remove LiveData type in birdDAO.kt and change Fragment.kt like this:
private fun loadBirdData(jDate: Int) {
val listBirdByDate = birdViewModel.getBirdByDate(jDate)
if(listBirdByDate.isNotEmpty) {
//TODO Processing Data
}
else
{
//TODO: download n insert new data
}
}
but the listBirdByDate.isNotEmpty line gave me this error:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public inline fun <T> Array<out TypeVariable(T)>.isNotEmpty(): Boolean defined in kotlin.collections
Or what is the best way to get my goal done? Thx
Instead of returning list of birds , you can create another query to get the count of found entries for particular date and in the view Model , I guess, you want to start downloading data from server once your verify that the count is zero.
You can change your viewModel from
fun getBirdByDate(rDate: Int) =
birdRepository.getBirdBySingleDate(rDate)
to
fun getBirdByDate(rDate :Int){
viewModelScope.launch {
var count= 2 //we don't want two in db though
//withContext and also with main dispatcher we assure that the count
//vairable gets updated , I recently came to know that we can actually
// use main as well immediate on Dispatcher inside viewmodel , so
//including it :P
//Thanks to #commonware
withContext(Dispatchers.Main.immediate){
count = //your repository function to get dao using Dispatche.IO
}
if(count == 0){
//start downloading from server and update data in db
}else if(count==1){
//get the list from db
}else
{
//show error
}
}
}
you can actually do better logic for if and else (use when for kotlin), I am trying to give you some idea as I am also new to android.
I would like to save data into the real-time database of Firebase, but I run into a problem. I've only been working with Kotlin for 1 month, and unfortunately I don't know how to save data to the real-time database in this form, as shown in the picture.
My current code is this:
data class userData(
val username: String,
val uid: String,
)
database = FirebaseDatabase.getInstance().getReference("Userdata")
val user = uid?.let {
userData("Test1", it)
}
if (uid != null) {
database.child(uid).setValue(user).addOnSuccessListener {
Toast.makeText(this, "Success saved", Toast.LENGTH_LONG).show()
}.addOnFailureListener {
Toast.makeText(this, "Failed", Toast.LENGTH_LONG).show()
}
}
My database. The same way, I would like to be able to save it with Kotlin
https://i.stack.imgur.com/fbAsC.png
The problem is, I don't know how to save the data object to the database, is there an easy way?
I would suggest you to improve your data class for userData model like below because there are some cases, you will have error about it in future.
data class userData(
val username: String? = null,
val uid: String? = null,
)
To answer your question, you need to create a new data class for the data model like below.
data class UserDataExtra(
val banned: Boolean? = null
)
Next you just need to implement inside the addOnSuccessListener like below.
if (uid != null) {
database.child(uid).setValue(user).addOnSuccessListener {
//Here you will update new model for userDataExtra
val userExtra = UserDataExtra (false)
database.child(uid).child("UserDataExtra").setValue(userExtra )..addOnSuccessListener {
Toast.makeText(this, "Success saved with data extra!", Toast.LENGTH_LONG).show()
}
}.addOnFailureListener {
Toast.makeText(this, "Failed", Toast.LENGTH_LONG).show()
}
}
Get instance of your database:
val database = Firebase.database.reference
The call to write to your database:
database.child("UserData").child(uid).setValue(user)
To Read from your database once:
database.child("UserData").child(uid).get().addOnSuccessListener {
Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
Log.e("firebase", "Error getting data", it)
}
This article has the solution to what you're looking for.
https://firebase.google.com/docs/database/android/read-and-write?hl=en&authuser=0#read_data
It is also possible to listen for changes in your database and update accordingly mentioned in the article above.
I think what you forgot is to tell the table name inside the database.
You should try using database.child(tableName).child(uid).setValue(user)
and then the addOnSuccessListener
I'm pretty new in the world of MVI pattern. So I'm trying to understand how fit together all the pieces.
I have an app that I structured using MVI pattern (or at least it was what I was meant to do). I have my fragment (I used navigation component but at the moment focus just on one fragment), which is supported by its own ViewModel. Then I have a repository class where all viewmodels retrieve data. Repository has 2 source of data, a web API and a local DB used as cache of data, I used Room for DB management.
I tried different approaches to the problem. At the moment I have done in this way:
In the DAO I used this instruction to retrieve data from the DB:
#Query("SELECT * FROM Users WHERE idTool=:idTool AND nickname LIKE '%' || :query || '%'")
fun users(idTool: Int, query: String) : Flow<List<User>>
Then in my repository I simple get this query to forward to ViewModels:
fun usersFlow(idTool: Int, query: String) = userDao.users(idTool, query)
In the ViewModel I created two MutableLiveData, coordinated by a MediatorLiveData:
val nicknameQuery = MutableStateFlow("")
private val nicknameQueryFlow = nicknameQuery.flatMapLatest {
repository.usersFlow(idToolQuery.value, it)
}
val idToolQuery = MutableStateFlow(DEFAULT_TOOL_ID)
private val idToolQueryFlow = idToolQuery.flatMapLatest {
repository.usersFlow(it, nicknameQuery.value)
}
val users = MediatorLiveData<List<User>>()
init {
users.addSource(nicknameQueryFlow.asLiveData()) {
users.value = it
}
users.addSource(idToolQueryFlow.asLiveData()) {
users.value = it
}
fetchUsers()
}
In this way, from my fragment, I can simply update nicknameQuery or idToolQuery to have an updated list in my RecyclerView. My first doubt is that in this way the fetch of data from my DB is done 2 times, one time for each mutable, but I'd like to retrieve data just one on the app opening (maybe the solution fro this is just check in the nicknameQuery that current query is different from the passed one, in this way since at the beginning current query is empty and it pass an empty query, it is bypassed).
In the Init method of ViewModel, I also call fetchUsers():
private fun fetchUsers() {
viewModelScope.launch {
repository.fetchUsers(DEFAULT_TOOL_ID).collect {
_dataState.value = it
}
}
}
This method checks into the database if there are already cached users with this specific idTool, if not it fetches them from the web and it stores retrieved data into the DB. This is the method inside my repository class:
suspend fun fetchUsers(
idTool: Int,
forceRefetch: Boolean = false
): Flow<DataState<List<User>>> = flow {
try {
var cachedUser = userDao.users(idTool, "").first()
val users: List<User>
if(cachedUser.isEmpty() || forceRefetch) {
Log.d(TAG, "Retrieve users: from web")
emit(DataState.Loading)
withContext(Dispatchers.IO) {
appJustOpen = false
val networkUsers =
api.getUsers(
idTool,
"Bearer ${sessionClient.tokens.accessToken.toString()}"
)
users = entityMapper.mapFromEntitiesList(networkUsers)
userDao.insertList(users)
}
} else {
users = cachedUser
}
emit(DataState.Success(users))
} catch (ex: Exception) {
emit(DataState.Error(ex))
}
}
This method checks if I have already users inside the DB with this specific idTool, if not it fetches them from API. It uses a DataState to update the UI, based on the result of the call. During the fetch of data, it emits a Loading state, this shows a progress bar in my fragment. If data is correctly fetched it emits a Success, and the fragment hides the progress bar to shows the recycler view. This is done in the following way. In my ViewModel I have this mutable state
private val _dataState = MutableLiveData<DataState<List<User>>>()
val dataState: LiveData<DataState<List<User>>> get() = _dataState
As you saw above, my fetch method is
private fun fetchUsers() {
viewModelScope.launch {
repository.fetchUsers(DEFAULT_TOOL_ID).collect {
_dataState.value = it
}
}
}
And finally in my fragment I have:
userListViewModel.dataState.observe(viewLifecycleOwner, { dataState ->
when (dataState) {
is DataState.Success -> {
showUserList()
}
is DataState.Error -> {
Log.e("TEST", dataState.exception.toString())
hideLoader()
Toast.makeText(activity, "Error retrieving data: ${dataState.exception}", Toast.LENGTH_LONG).show()
}
is DataState.Loading -> {
showLoader()
}
else -> {
// Do Nothing in any other case
}
}
})
At this moment Success state takes a list of users, but this list is there from a previous approach, at the moment it is useless since after data is fetched list is inserted into the DB, and I have a Flow to the DB which takes care to update the UI. In this way when I change idTool, when I change query, when I remove a user, the view is always notified
Is this approach correct?
Before this, I used another approach. I returned not a flow from my DB but just a List. Then my fetchUsers always returned a DataState<List>, it checked in the DB and if didn't found anything it fetched data from the web and returned that list. This approach caused me some problems, since every time I changed idTool or query, I always had to call fetchUsers method. Even if a user was removed from database, views didn't get notified since I didn't have a direct flow with the DB.
I'm trying to use requery https://github.com/requery/requery library with Kotlin and SQLite backend. I have a sql dump, which I want to write to sqlite database in the first launch of an application, and then I want to map data classes to database entities with requery.
Here is data source initialization with table creation:
if (!(DataStorage.isDbInitialized(context))) {
val db = writableDatabase
val inputStream = context?.resources?.openRawResource(R.raw.dump)
val reader = BufferedReader(InputStreamReader(inputStream))
val builder = StringBuilder()
var line : String?
var end = false
while (!end) {
line = reader.readLine()
if(line == null) {
end = true
} else {
builder.append(line)
}
}
db.execSQL(builder.toString())
onCreate(db)
DataStorage.setDbInitialized(context)
}
I have to derive this class from both SqlitexDatabaseSource and CommonDataSource to use with Kotlin. SQL query execuled successfully, but when I trying to select all objects from database, this request returns zero sized list:
val result : Result<Feat> = (application as MainApp).dataStore.select(Feat::class).get()
result.each {
Log.d("TAG", it.name)
}
DTO created as described in documentation:
https://github.com/Syjgin/PathfinderFeats/blob/master/app/src/main/java/com/syjgin/pathfinderfeats/model/Feat.kt
Is it possible to initialize requery data with sql dump, or I have to create DTO for each row and submit it via insert method?