With retrofit I get response LevelsEntity but if I get error it get me ResponseError, NOTE: I cant merge LevelsEntity and ResponseError together in one entity.
LevelsEntity:
class LevelsEntity : ArrayList<LevelsEntityItem>()
LevelsEntityItem:
data class LevelsEntityItem(
#SerializedName("category")
val category: Int? = null,
#SerializedName("completed")
val completed: Boolean? = null,
#SerializedName("completionhascriteria")
val completionhascriteria: Boolean? = null
)
ResponseError:
data class ResponseError(
#SerializedName("errorcode")
val errorcode: String? = null,
#SerializedName("exception")
val exception: String? = null,
#SerializedName("message")
val message: String? = null
)
And I create bellow class for get multiple data like bellow:
class BaseLevelsEntity<LevelsEntity, ResponseError> {
var levelsEntity: LevelsEntity? = null
var responseError: ResponseError? = null
val isSuccess: Boolean
get() = responseError == null
}
And in my #POST of retrofit is:
#POST("/webservice/rest/server.php")
suspend fun getPopularLevelsInLessonsF(
#Query("mdwsrestformat") mdwsrestformat: String?,
#Field("wsfunction") wsfunction: String?,
#Field("wstoken") wstoken: String?,
#Field("userid") userid: Int?
): Call<BaseLevelsEntity<LevelsEntity, ResponseError>>
But I cant get any result in my impl:
class LessonsRepositoryImpl(
private val lessonsRemoteDatasource: LessonsRemoteDatasource
) : LessonsRepository {
override suspend fun getLevelsInLessonsF(
wstoken: String,
userid: Int
): Resource<BaseLevelsEntity<LevelsEntity, ResponseError>> {
return responseToResource(lessonsRemoteDatasource.getLevelsValueInLessonsF(wstoken, userid).execute())
}
private fun responseToResource(response: Response<BaseLevelsEntity<LevelsEntity, ResponseError>>): Resource<BaseLevelsEntity<LevelsEntity, ResponseError>> {
if (response.isSuccessful) {
if (response.body() != null) {
response.body()?.let { result ->
if (!result.levelsEntity.isNullOrEmpty()) {
if (result.levelsEntity!!.size > 0) {
return Resource.Success(result)
}
} else if (result.responseError != null) {
return Resource.Error(result.responseError?.errorcode ?: "unknown")
}
}
} else {
return Resource.Error("unknown_info")
}
}
return Resource.Error(response.message())
}
}
Normally response should be in common format.
If cannot do this from backend then you can receive response as JsonObject and then check the key manually in repository to decide if it is success or error response. Based on that you can then convert the response to object with gson.
I don't know how to pass the bitmap as byteArray in the code below. I have added the parameter in the note model class and it is of type byte array. Now how do I use the typeConverter to convert the bitmap to bytearray and pass it? Please help.
The lines in which the parameter is to be added are in "EditActivity", under savebutton.onClicklistener, where I fill the parameters for the modelClass.
Here's my TypeConverter :
class ImageConverter {
#TypeConverter
fun getStringFromBitmap(bitmap: Bitmap): ByteArray {
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
return outputStream.toByteArray()
}
#TypeConverter
fun getBitmapFromString(byteArray: ByteArray): Bitmap{
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
}
And here's my "EditActivity" that adds image from gallery to the activity:
class AddEditNoteActivity : AppCompatActivity() {
lateinit var backButton: FloatingActionButton
lateinit var editTitle: EditText
lateinit var editDesc: EditText
lateinit var saveButton: FloatingActionButton
lateinit var viewModel: JourViewModel
lateinit var addImageButton: FloatingActionButton
lateinit var theimage: ImageView
lateinit var ImageURI: Uri
lateinit var bitmap: Bitmap
var noteID= -1
companion object{
const val IMAGE_REQ_CODE=100
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_edit_note)
editTitle = findViewById(R.id.editNoteTitle)
editDesc = findViewById(R.id.editNoteDescription)
saveButton=findViewById(R.id.jourSaveButton)
backButton=findViewById(R.id.backButton)
addImageButton=findViewById(R.id.jourAddImgButton)
theimage=findViewById(R.id.imageView1)
viewModel= ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
).get(JourViewModel::class.java)
val noteType = intent.getStringExtra("noteType")
if(noteType.equals("Edit")){
val noteTitle = intent.getStringExtra("noteTitle")
val noteDesc = intent.getStringExtra("noteDescription")
noteID= intent.getIntExtra("noteID",-1)
editTitle.setText(noteTitle)
editDesc.setText(noteDesc)
}
saveButton.setOnClickListener{
val noteTitle= editTitle.text.toString()
val noteDesc= editDesc.text.toString()
val storebyte=
if(noteType.equals("Edit")){
if(noteTitle.isNotEmpty() && noteDesc.isNotEmpty()){
val sdf= SimpleDateFormat("MMM, dd,yyyy")
val currentDate:String= sdf.format(Date())
val updateNote = Note(noteTitle, noteDesc, currentDate, )
updateNote.id=noteID
viewModel.updateNote(updateNote)
Toast.makeText(this,"Updated!",Toast.LENGTH_SHORT).show()
startActivity(Intent(applicationContext,MainActivity::class.java))
this.finish()
}else{
Toast.makeText(this,"Please fill both the columns!",Toast.LENGTH_SHORT).show()
}
}else{
if(noteTitle.isNotEmpty() && noteDesc.isNotEmpty()){
val sdf= SimpleDateFormat("MMM dd,yyyy")
val currentDate:String= sdf.format(Date())
viewModel.addNote(Note(noteTitle,noteDesc,currentDate,))
Toast.makeText(this,"Added!",Toast.LENGTH_SHORT).show()
startActivity(Intent(applicationContext,MainActivity::class.java))
this.finish()
}else{
Toast.makeText(this,"Please fill both the columns!",Toast.LENGTH_SHORT).show()
}
}
}
backButton.setOnClickListener{
val intent = Intent(this#AddEditNoteActivity,MainActivity::class.java)
startActivity(intent)
this.finish()
}
addImageButton.setOnClickListener{
pickImageGallery()
}
}
private fun pickImageGallery() {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/"
startActivityForResult(intent, IMAGE_REQ_CODE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?){
super.onActivityResult(requestCode, resultCode, data)
if(requestCode== IMAGE_REQ_CODE && resultCode==RESULT_OK){
ImageURI = data?.data!!
if (ImageURI!=null){
theimage.setImageURI(data?.data)
try {
bitmap =MediaStore.Images.Media.getBitmap(contentResolver, ImageURI)
theimage.setImageBitmap(bitmap)
}catch(exception: IOException){
exception.printStackTrace()
}
}
}
}
}
ModelClass:
#Entity(tableName = "jourTable")
class Note(
#ColumnInfo(name = "title") val jourTitle:String,
#ColumnInfo(name = "description") val jourDescription:String,
#ColumnInfo(name = "date") val jourDate:String,
#ColumnInfo(name = "image", typeAffinity = ColumnInfo.BLOB) val jourImage: ByteArray
) {
#PrimaryKey(autoGenerate = true)var id=0
}
Now how do I use the typeConverter to convert the bitmap to bytearray and pass it?
Typically you don't use the Type Converter, you let Room know about it and Room then uses it.
So taking getStringFromBitmap(bitmap: Bitmap): ByteArray (should really be getByteArrayFromBitmap)
Then in the class annotaed with #Database ADD an #TypeConverters annotation (NOTE the plural not singular) that defines the class where the Type Converters are located e.g :-
#TypeConverters(ImageConverter::class) //<<<<< ADDED
#Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
....
Now this will convert a BitMap to a ByteArray as such you don't code the type as ByteArray but instead as Bitmap.
You can use the type converter and bypass Room's automatic use of it.
Let's say you have (uses both) :-
#Entity(tableName = "jourTable")
class Note(
#ColumnInfo(name = "title") val jourTitle:String,
#ColumnInfo(name = "description") val jourDescription:String,
#ColumnInfo(name = "date") val jourDate:String,
#ColumnInfo(name = "image", typeAffinity = ColumnInfo.BLOB) val jourImage: Bitmap, //<<<<< will use the TypeConverter
#ColumnInfo(name = "altImage") val jourAltImage: ByteArray //<<<<< will not use the TypeConverter
) {
#PrimaryKey(autoGenerate = true)var id=0
}
And then have an #Dao annotated interface or abstract class such as :-
#Dao
abstract class AllDao {
#Insert
abstract fun insert(note: Note): Long
}
Then in an activity you could use:-
dao.insert(
Note
("Title1","Description1","2021-02-08",
bm, /*<<<<< ROOM will know to get the ByteArray from the Bitmap and utilise the TypeConverter */
ImageConverter().getStringFromBitmap(bm) /* use the TypeConverter to get a ByteArray (not needed)*/
)
)
As a working example consider the following :-
val bm = BitmapFactory.decodeByteArray(ByteArray(0),0,0) // results in null Bitmap
val db = TheDatabase.getInstance(this);
val dao = db.getAllDao()
dao.insert(
Note
("Title1","Description1","2021-02-08",
bm, /*<<<<< ROOM will know to get the ByteArray from the Bitmap and utilise the TypeConverter */
ImageConverter().getStringFromBitmap(bm) /* use the TypeConverter to get a ByteArray (not needed)*/
)
)
and a modified TypeConverter which handles the null Bitmap by returning a ByteArray of 10 0 bytes i.e. :-
#TypeConverter
fun getStringFromBitmap(bitmap: Bitmap?): ByteArray {
if (bitmap == null) {
return ByteArray(10)
}
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
return outputStream.toByteArray()
}
Then, when run, the result as per App Inspection is :-
i.e The image column is the 10 0 bytes as produced by the TypeConverter automatically invoked by Room. The altimage column likewise is 10 0 bytes but via the TyoeConverter manually invoked.
When extracting data again Room will automatically invoke the 2nd TypeConverter for the image column/field the altImage column/field would be returned as a ByteArray.
UPDATE:
With the help of #MikeT 's response I was able to store data in the database. And
with the help of this I was able to pass bitmap from one activity to another to load the image.
The function is working now, if anyone wants to check out the issue/repo, here you go.
I'm struggling with a ViewModel issue, I'm quite new to them.
I need to be able to access a Json file in my assets folder, so I'm using androidViewModel.
But I also need to pass a category into the viewModel, so I can filter the json file by that category.
What I've tried so far.
building a viewModeFactory
Creating a ResourcesProvider
creating a seperate viewModel for each category
Whats the best way to achieve this?
Here's my current viewModel
class GuideViewModel(application: Application) : AndroidViewModel(application) {
private val _name = MutableLiveData<String>()
val name: LiveData<String>
get() = _name
private val _difficulty = MutableLiveData(3)
val difficulty: LiveData<Int>
get() = _difficulty
private val _date = MutableLiveData<Long>(3010)
val date: LiveData<Long>
get() = _date
private val _graphic = MutableLiveData<String>()
val graphic: LiveData<String>
get() = _graphic
private var brewerDetails: MutableLiveData<MovieObject>? = null
fun getDetails(CurrentMovie: String): MutableLiveData<MovieObject> {
brewerDetails = MutableLiveData<MovieObject>()
loadJsonData(CurrentMovie)
return brewerDetails as MutableLiveData<MovieObject>
}
private fun loadJsonData(CurrentMovie: String) {
var CurrentMovieObject = MovieObject()
try {
val jsonString = loadJsonDataFromFile()
val json = JSONObject(jsonString)
val jsonBrewers = json.getJSONArray("movies")
for (index in 0 until jsonBrewers.length()) {
if (jsonBrewers.getJSONObject(index).getString(KEY_NAME) == "VARIABLE") { << need to pass a variable into viewModel somehow
val name = jsonBrewers.getJSONObject(index).getString(KEY_NAME)
val difficulty = jsonBrewers.getJSONObject(index).getInt(KEY_DIFFICULTY)
val date = jsonBrewers.getJSONObject(index).getLong(KEY_DATE)
val graphic = jsonBrewers.getJSONObject(index).getString(KEY_GRAPHIC)
_name.value = name
_difficulty.value = difficulty
_date.value = date
_graphic.value = graphic
}
}
} catch (e: JSONException) {
}
}
private fun loadJsonDataFromFile(): String {
var json = ""
try {
val input = getApplication<Application>().assets.open("movies.json") << need application to open the json file
val size = input.available()
val buffer = ByteArray(size)
input.read(buffer)
input.close()
json = buffer.toString(Charsets.UTF_8)
} catch (e: IOException) {
e.printStackTrace()
}
return json
}
}
I'm calling data from Breaking bad API https://www.breakingbadapi.com/api/character/random
I'm unable to get data. I think it's because the main Response file has square brackets that I need to call first. But I don't know how to call it. Can I get some help?
Here's my API interface
interface APIRequest {
#GET("character/random")
suspend fun getInfo() : Response<List<ResponseBB>>
}
ResponseBB Class
data class ResponseBB(
#field:SerializedName("ResponseBB")
val responseBB: List<ResponseBBItem?>? = null
)
data class ResponseBBItem(
#field:SerializedName("birthday")
val birthday: Any? = null,
#field:SerializedName("img")
val img: String? = null,
#field:SerializedName("better_call_saul_appearance")
val betterCallSaulAppearance: Any? = null,
#field:SerializedName("occupation")
val occupation: List<String?>? = null,
#field:SerializedName("appearance")
val appearance: List<Int?>? = null,
#field:SerializedName("portrayed")
val portrayed: String? = null,
#field:SerializedName("name")
val name: String? = null,
#field:SerializedName("nickname")
val nickname: String? = null,
#field:SerializedName("char_id")
val charId: Int? = null,
#field:SerializedName("category")
val category: String? = null,
#field:SerializedName("status")
val status: String? = null
)
Client object
object Client {
val gson = GsonBuilder().create()
val retrofit = Retrofit.Builder()
.baseUrl("https://www.breakingbadapi.com/api/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val api = retrofit.create(APIRequest::class.java)
}
Here's my function to call result in the main activity
class MainActivity : AppCompatActivity() {
private var TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getCharacterInfo()
linearLayout.setOnClickListener {
getCharacterInfo()
}
}
private fun getCharacterInfo() {
GlobalScope.launch(Dispatchers.IO) {
try {
val response = Client.api.getInfo()
if (response.isSuccessful) {
val data = response.body()
Log.d(TAG, data.toString())
withContext(Dispatchers.Main) {
Picasso.get().load(data!!.img).into(ivImage)
tvName.text = data.name
tvOccupation.text = data.toString()
tvActor.text = data.toString()
tvAppearance.text = data.appearance.toString()
tvStatus.text = data.status
}
}
}
catch (e:Exception){
withContext(Dispatchers.Main){
Toast.makeText(applicationContext, "Cannot Load Data" , Toast.LENGTH_LONG).show()
}
}
}
}
}
I see that you try to use coroutines in retrofit, I recommend that you do not work with Response, change it to call and remove the suspend.
interface APIRequest {
#GET("character/random")
fun getInfo() : Call<List<ResponseBB>>
}
In your Global Scope you can call it this way:
GlobalScope.launch {
try{
val response = Client.api.getInfo().await()
}catch(e:Exception){}
}
you can use the version 2.9.0 in retrofit and gson Converter
I have a parameterized base class
#JsonClass(generateAdapter = true)
data class BaseResponse<T>(
#Json(name = "message")
val message: String?,
#Json(name = "data")
val data: T? = null
)
I want to get parse a JSON string and get the message value
private inline fun <reified T> getMessage(): String? {
return try {
val jsonStr = "{\"message\":\"Email or password not provided\"}"
val types = Types.newParameterizedType(
BaseResponse::class.java,
T::class.java
)
val moshiAdapter = Moshi.Builder().build().adapter(types)
val baseResponse = moshiAdapter.fromJson(jsonStr)
baseResponse?.message
} catch (exception: Exception) {
null
}
}
Got compile error at the adapter function
How I call this function
val str = getMessage<Any>()
You're not specifying that you're parsing a BaseResponse, just replace your adapter creation by this
val moshiAdapter = Moshi.Builder().build().adapter<BaseResponse<T>>(types)