How to pass a generic class to GsonRequest<T> constructor - java

How can I pass a generic class to GsonRequest class? Here is my api's response model.
class APIResponse<T> {
var status:Boolean
var data:T?
var collection: ArrayList<T>?
var message:String?
constructor(status:Boolean, data:T?, collection: ArrayList<T>?, message:String?){
this.status = status
this.data = data
this.collection = collection
this.message = message
}
}
And these are the kotlin models of tables in my database. There are used for converting json to models.
class ControlCategory {
var id:Int = 0
var name:String? = null
var control_points:ArrayList<ControlPoint> = ArrayList()
constructor(id:Int, name:String, control_points:ArrayList<ControlPoint>) {
this.id = id
this.name = name
this.control_points = control_points
}
}
class ControlPoint {
var id:Int
var name:String
var control_category: ControlCategory
constructor(id:Int, name:String, control_category:ControlCategory) {
this.id = id
this.name = name
this.control_category = control_category
}
}
Here is my GsonRequest class.
open class GsonRequest<T>(
url: String,
private val clazz: Class<T>,
private val headers: MutableMap<String, String>?,
private val listener: Response.Listener<T>,
errorListener: Response.ErrorListener
) : Request<T>(Method.GET, url, errorListener) {
protected val gson = Gson()
override fun getHeaders(): MutableMap<String, String> = headers ?: super.getHeaders()
override fun deliverResponse(response: T) = listener.onResponse(response)
override fun parseNetworkResponse(response: NetworkResponse?): Response<T> {
return try {
val json = String(
response?.data ?: ByteArray(0),
Charset.forName(HttpHeaderParser.parseCharset(response?.headers)))
Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response))
} catch (e: UnsupportedEncodingException) {
Response.error(ParseError(e))
} catch (e: JsonSyntaxException) {
Response.error(ParseError(e))
}
}
}
How can I pass APIResponse<ControlCategory> to GsonRequest class' :
val controlPointsRequest = GsonRequest<APIResponse<ControlCategory>>(
getString(R.string.apiUrl)+"control-categories",
object: TypeToken<APIResponse<ControlCategory>>(){}.type,
headers,
listener,
error
);
When I pass object: TypeToken<APIResponse<ControlCategory>>(){}.type it gives error.

Related

Getting individual objects in json request Body in key, value format

Please help the beginner.
When sending a request to the requestBody json I get:
key: "{
"grant_type" : "client_credentials",
"client_id" : "OC_CLIENT_ID",
"client_secret" : "OC_CLIENT_SECRET"
}"
value: ""
and it is required that the requestBody looks like this
{
key:"grant_type"
value: "client_credentials"
}
{
key:"client_id"
value: "OC_CLIENT_ID"
}
{
key:"client_secret"
value: "OC_CLIENT_SECRET"
}
The server sent for some reason not a set of parameters, but simply stuck json into the name of the first parameter.
The code is:
#Path("/")
public interface OAuth2RequestService {
#POST
AccessTokenRecord create(#HeaderParam(value = "Content-type") String type,
#FormParam(value = "grant_type") String grantType,
#FormParam(value = "client_id") String clientId,
#FormParam(value = "client_secret") String clientSecret);
}
#Override
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public OAuth2Interceptor getAccessTokenInterceptor(Account account,
Boolean isGeneralEpaService) {
if (openAmIsEnabled(account)) {
final FeignOptions<OAuth2RequestService> options =
new FeignOptions<>(getAccessTokenUrl(account, isGeneralEpaService));
final AccessTokenRecord accessTokenRecord = workerRestService(options)
.create(HEADER_TYPE, CLIENT_CREDENTIALS, getClientId(isGeneralEpaService),
getClientSecret(isGeneralEpaService));
logger.infof("OAuth2 access token retrieval succeeded.");
return new OAuth2Interceptor(accessTokenRecord);
}
final AccessTokenRecord accessTokenRecord = new AccessTokenRecord();
accessTokenRecord.setAccessToken(getOsDefaultAccessToken(account));
accessTokenRecord.setTokenType(TOKEN_TYPE);
return new OAuth2Interceptor(accessTokenRecord);
}
private OAuth2RequestService workerRestService(
final FeignOptions<OAuth2RequestService> options) {
final Request.Options requestOptions =
new Request.Options(options.getConnectionTimeOut(), options.getReadTimeOut());
return Feign.builder().options(requestOptions).client(new OkHttpClient())
.contract(new JAXRSContract()).encoder(new JacksonEncoder())
.decoder(new JacksonDecoder()).decode404()
.target(OAuth2RequestService.class, options.getHostUrl());
}
I have tried several options with #QueueParam #FormParam

How to change base URL using retrofit2 and koin 2.0

I have a query that returns a list of servers, and the user can select the server he needs.
Googling did not help, almost no results.
Tell me how to implement basic URL spoofing in real time using Koin and Retrofit?
My Modules:
fun createMainModule(context: Context) = module {
single(named(APP_CONTEXT)) { context }
single(named(RESOURCES)) { context.resources }
single(named(REPOSITORY)) {
Repository(get(named(RETROFIT)))
}
}
fun createNetworkModule(baseUrl: String) = module(override = true) {
single(named(TOKEN_INTERCEPTOR)) { createTokenInterceptor(get(named(DATA_PROVIDER))) }
single(named(OK_HTTP)) { createOkHttpClient(get(named(TOKEN_INTERCEPTOR))) }
single(named(GSON)) { createGson() }
single(named(RETROFIT)) {
createRetrofit(
get(named(RESOURCES)),
get(named(LOG_OUT_SUBJECT)),
get(named(GSON)),
baseUrl,
get(named(OK_HTTP))
)
}
I resolve my problem with comment #sonnet
This code:
class ChangeableBaseUrlInterceptor : Interceptor {
#Volatile
private var host: HttpUrl? = null
fun setHost(host: String) {
this.host = host.toHttpUrlOrNull()
}
fun clear() {
host = null
}
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
var request = chain.request()
host?.let {
val newUrl = request.url.newBuilder()
.scheme(it.scheme)
.host(it.toUrl().toURI().host)
.port(it.port)
.build()
request = request.newBuilder().url(newUrl).build()
}
return chain.proceed(request)
}
}

How to download image using Retrofit or Picasso via HTTP POST method

I have a HTTP post request, it takes a JSON with the following content as request body:
{
"UserName":"ApiService",
"Password":"BandarAndroid",
"AndroidId":"15df3b3a90XXXXX",
"ContentId":"704",
"frame":"1"
}
After requesting to the server, I get only one image in response(Instead of anything (like JSON)).
The appropriate image is created on request and has no web address.
my service Address is :
http://xyz.website.com/api/DownloadFileForAndroid
and my response header is :
cache-control →no-cache
content-length →29837
content-type →image/png
date →Mon, 09 Sep 2019 08:42:23 GMT
expires →-1
pragma →no-cache
server →Microsoft-IIS/8.5
x-aspnet-version →4.0.30319
x-powered-by →ASP.NET
I don't know whether to use retrofit or Picasso to get this photo,
in Picasso: I can't send the amount of JSON in the request body.
in retrofit: I can't get the photo without the url (Address to point to the photo like www.site.com/a.jpg)
As you requested, I am converting my previous solution (Kotlin) to
Java
Example 1: Picasso
public void loadImageWithPicasso(ImageView imageView) {
Picasso.Builder builder = new Picasso.Builder(imageView.getContext());
RequestCreator picassoImageLoader = createPicassoLoader(
builder,
ImageRequest.DEFAULT_JSON_BODY,
"http://shop.atiafkar.ir/api/DownloadFileForAndroid"
);
picassoImageLoader.into(imageView);
}
public RequestCreator createPicassoLoader(Picasso.Builder builder, String body, String url) {
return builder.downloader(new OkHttp3Downloader(createPicassoCallFactory(body)))
.build()
.load(url);
}
private okhttp3.Call.Factory createPicassoCallFactory(String jsonBody) {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
final RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonBody);
return new okhttp3.Call.Factory() {
#Override
public okhttp3.Call newCall(Request request) {
Request.Builder builder = request.newBuilder();
builder.post(requestBody);
builder.addHeader("Content-Type", "application/json");
return okHttpClient.newCall(builder.build());
}
};
}
Example 2: Retrofit
public void loadImageWithRetrofit(ImageView imageView) {
final RetrofitImageLoader imageLoader = new RetrofitImageLoader(imageView);
RemoteApi api = RemoteApi.Factory.create();
api.getImage(ImageRequest.DEFAULT_BODY).enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
ResponseBody body = response.body();
if (response.isSuccessful() && body != null) {
imageLoader.execute(body.byteStream());
} else {
Log.d(TAG, "Retrofit onResponse(): CODE = [" + response.code() + "], MESSAGE = [" + response.message() + "]");
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.d(TAG, "Retrofit onFailure(): t = [" + t + "]");
}
});
}
RetrofitImageLoader class
public class RetrofitImageLoader extends AsyncTask<InputStream, Integer, Bitmap> {
private ImageView imageView;
private RetrofitImageLoader(ImageView imageView) {
this.imageView = imageView;
}
#Override
protected Bitmap doInBackground(InputStream... inputStreams) {
return BitmapFactory.decodeStream(inputStreams[0]);
}
#Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
imageView.setImageBitmap(bitmap);
}
}
RemoteApi interface
public interface RemoteApi {
#Streaming // Important
#POST("/api/DownloadFileForAndroid")
Call<ResponseBody> getImage(#Body ImageRequest body);
class Factory {
private static RemoteApi mInstance;
public static RemoteApi create() {
if (mInstance == null) {
mInstance = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://shop.atiafkar.ir")
.build()
.create(RemoteApi.class);
}
return mInstance;
}
}
}
ImageRequest model class
public class ImageRequest{
public static final ImageRequest DEFAULT_BODY;
public static final String DEFAULT_JSON_BODY;
static {
DEFAULT_BODY = new ImageRequest();
DEFAULT_BODY.setAndroidId("15df3b3a90dc5688");
DEFAULT_BODY.setContentId("704");
DEFAULT_BODY.setFrame("1");
DEFAULT_BODY.setPassword("BandarAndroid");
DEFAULT_BODY.setUserName("ApiService");
DEFAULT_JSON_BODY = new Gson().toJson(DEFAULT_BODY, ImageRequest.class);
}
#SerializedName("UserName")
private String userName;
#SerializedName("ContentId")
private String contentId;
#SerializedName("AndroidId")
private String androidId;
#SerializedName("Password")
private String password;
#SerializedName("frame")
private String frame;
public void setUserName(String userName){
this.userName = userName;
}
public void setContentId(String contentId){
this.contentId = contentId;
}
public void setAndroidId(String androidId){
this.androidId = androidId;
}
public void setPassword(String password){
this.password = password;
}
public void setFrame(String frame){
this.frame = frame;
}
}
I don't know whether to use retrofit or Picasso to get this photo
It is better to use Picasso otherwise you have to write a lot of codes to load images efficiently if you download them using Retrofit.
You would be happy to know that you can use both Retrofit and Picasso depending on your choice to load images from that API.
Before going ahead with example, I want to clear one thing up that you had a misconception that you needed to send the above-mentioned JSON data as header but after playing around with the API I figured out that it takes the JSON as request body.
Examples
RemoteApi.kt
interface RemoteApi {
// Retrofit
#Streaming // Important
#POST("/api/DownloadFileForAndroid")
#Throws(Exception::class)
suspend fun getImage(#Body body: ImageRequest): ResponseBody?
companion object {
// Retrofit
fun create(): RemoteApi {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://shop.atiafkar.ir")
.build()
return retrofit.create(RemoteApi::class.java)
}
// Picasso
fun getPicassoImageRequest(
builder : Picasso.Builder,
body: String,
url: String = "http://shop.atiafkar.ir/api/DownloadFileForAndroid"
) = builder.downloader(OkHttp3Downloader(getPicassoCallFactory(body)))
.build()
.load(url)
// Picasso
private fun getPicassoCallFactory(jsonBody : String): Call.Factory {
return Call.Factory { request ->
OkHttpClient().run {
RequestBody.create(MediaType.parse("application/json"), jsonBody).let {
newCall(request.newBuilder()
.post(it)
.addHeader("Content-Type", "application/json")
.build()
)
}
}
}
}
}
}
ImageRepository.kt
class ImageRepository(private val api: RemoteApi = RemoteApi.create()) {
companion object {
fun get() = ImageRepository()
}
// Retrofit
suspend fun downloadImage(body : ImageRequest = ImageRequest.default): Bitmap? {
return api.getImage(body)?.run {
withContext(Dispatchers.IO) {
bytes().let {
// to load bitmap efficiently follow the guideline provided by -
// https://developer.android.com/topic/performance/graphics/load-bitmap
// otherwise you may experience OutOfMemoryException
BitmapFactory.decodeByteArray(it, 0, it.size)
}
}
}
}
// Picasso
fun getPicassoImageLoader(
builder : Picasso.Builder,
body: ImageRequest = ImageRequest.default
) = RemoteApi.getPicassoImageRequest(builder, body.toJson())
}
ImageViewModel.kt
class ImageViewModel(private val repository: ImageRepository) : ViewModel() {
private val _progress = MutableLiveData<Boolean>()
val progress = _progress
// Retrofit
val liveImage = liveData {
_progress.value = true
emit(repository.downloadImage())
_progress.value = false
}
// Picasso
fun getPicassoImageLoader(builder: Picasso.Builder) = repository.getPicassoImageLoader(builder)
}
Finally,
ImageActivity.kt
class ImageActivity : AppCompatActivity() {
private lateinit var dataBinding : ActivityImageBinding
private val imageViewModel by lazy { ViewModelProviders.of(this, ImageViewModelFactory()).get(ImageViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_image)
dataBinding.lifecycleOwner = this
dataBinding.viewModel = imageViewModel
// Retrofit
imageViewModel.liveImage.observe(this, Observer {
dataBinding.imageView.setImageBitmap(it)
})
// Picasso
imageViewModel.getPicassoImageLoader(Picasso.Builder(this)).into(dataBinding.imageView2)
}
}
ImageRequest.kt
data class ImageRequest(
#field:SerializedName("UserName")
val userName: String? = null,
#field:SerializedName("ContentId")
val contentId: String? = null,
#field:SerializedName("AndroidId")
val androidId: String? = null,
#field:SerializedName("Password")
val password: String? = null,
#field:SerializedName("frame")
val frame: String? = null
) {
companion object {
val default = ImageRequest(
"ApiService",
"704",
"15df3b3a90dc5688",
"BandarAndroid",
"1"
)
}
}
fun ImageRequest.toJson() : String {
return Gson().toJson(this, ImageRequest::class.java)
}

Generic function to make post request in Volley

I work on developing an android app and I would like to make a generic function of volley post request, I write my function as bellow:
public fun <T> push(context: Context, url: String, myObject: T, completion: (response: String) -> Unit) {
val queue = Volley.newRequestQueue(context)
val sr = object : StringRequest(
Method.POST, url,
Response.Listener { response ->
println(response)
completion(response)
},
Response.ErrorListener { volleyError ->
Common.showVolleyError(volleyError, context)
}) {
override fun getParams(): Map<String, String> {
val params = myObject as HashMap<String, String>
return params
}
#Throws(AuthFailureError::class)
override fun getHeaders(): Map<String, String> {
val params = HashMap<String, String>()
params["Content-Type"] = "application/x-www-form-urlencoded"
params["X-Requested-With"] = "XMLHttpRequest"
return params
}
}
sr.retryPolicy = DefaultRetryPolicy(
0,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
queue.add(sr)
}
What I enforce is How to convert my serializable object to a HashMap<String, String>(), i.e. How to bind myObject to getParams() function,
Make a base class includes an abstract method returns Map<String, String> named for example getConvertedParams. This method should convert itself to Map<String, String> like:
val params = HashMap<String, String>()
params["attribute1"] = attribute1
params["attribute2"] = attribute2
...
return params
Every request object should extends that base class and override that method. In getParams where you send request, call getConvertedParams for your generic request object.
override fun getParams(): Map<String, String> {
val params = myObject.getConvertedParams()
return params
}
Also do not forget to change the method signature
public fun <BaseClassName> push(context: Context, url: String, myObject: BaseClassName, completion: (response: String) -> Unit)
Finally for any one may like to use this way, I rewrite function as below:
public fun <T> push(context: Context, url: String, myObject: T,myObjectType : Array<Field>, completion: (response: String) -> Unit) {
val myObjectAsDict = HashMap<String, String>()
val allFields = myObjectType //:Array<Field> = myObjectType!!::class.java.declaredFields
for ( field in allFields) {
if (!field.isAccessible) {
field.isAccessible = true
}
val value = field.get(myObject)
if (value != null)
{
if( field.name != "serialVersionUID") {
myObjectAsDict[field.name] = value.toString()
}
}
}
println(myObjectAsDict)
val queue = Volley.newRequestQueue(context)
val sr = object : StringRequest(
Method.POST, url,
Response.Listener { response ->
println(response)
completion(response)
},
Response.ErrorListener { volleyError ->
Common.showVolleyError(volleyError, context)
}) {
override fun getParams(): Map<String, String> {
val params = myObjectAsDict
return params
}
#Throws(AuthFailureError::class)
override fun getHeaders(): Map<String, String> {
val params = HashMap<String, String>()
params["Content-Type"] = "application/x-www-form-urlencoded"
params["X-Requested-With"] = "XMLHttpRequest"
return params
}
}
sr.retryPolicy = DefaultRetryPolicy(
0,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
queue.add(sr)
}
And using of it As below:
var myClass = MyClass()
VolleyFunctions.push(this,"URL",myClass, MyClass::class.java.declaredFields)
{
response->
myClass = Gson().fromJson(response, MyClass::class.java)
println("myClass.Name${myClass.name}")
}
Thanks faranjit for your answer and yours comments.

Json dynamic deserialization with jackson

I've already have a look at the question "Jackson dynamic property names" but it does not really answer to my question.
I want to deserialize something like this :
public class Response<T> {
private String status;
private Error error;
private T data;
}
but data can have different names since different services exist and return the same structure with some different data. For example 'user' and 'contract' :
{
response: {
status: "success",
user: {
...
}
}
}
or
{
response: {
status: "failure",
error : {
code : 212,
message : "Unable to retrieve contract"
}
contract: {
...
}
}
}
I'd like genericize my responses objects like this :
public class UserResponse extends Response<User> {}
I've tried the following but i'm not sure it is my use case or if don't use it in the good way :
#JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.CLASS)
#JsonSubTypes({#Type(value = User.class, name = "user"),
#Type(value = Contract.class, name = "contract")})
Finally, i've created a custom Deserializer. It works but i'm not satisfied:
public class ResponseDeserializer extends JsonDeserializer<Response> {
#Override
public Response deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Response responseData = new Response();
Object data = null;
for (; jp.getCurrentToken() != JsonToken.END_OBJECT; jp.nextToken()) {
String propName = jp.getCurrentName();
// Skip field name:
jp.nextToken();
if ("contract".equals(propName)) {
data = mapper.readValue(jp, Contract.class);
} else if ("user".equals(propName)) {
data = mapper.readValue(jp, User.class);
} else if ("status".equals(propName)) {
responseData.setStatus(jp.getText());
} else if ("error".equals(propName)) {
responseData.setError(mapper.readValue(jp, com.ingdirect.dg.business.object.community.api.common.Error.class));
}
}
if (data instanceof Contract) {
Response<Contract> response = new Response<Ranking>(responseData);
return response;
}
if (data instanceof User) {
Response<User> response = new Response<User>(responseData);
return response;
}
// in all other cases, the type is not yet managed, add it when needed
throw new JsonParseException("Cannot parse this Response", jp.getCurrentLocation());
}
}
Any idea to do this clean with annotations ? Thanks in advance !
Jackson framework provides inbuilt support for dynamic types.
//Base type
#JsonTypeInfo(property = "type", use = Id.NAME)
#JsonSubTypes({ #Type(ValidResponse.class),
#Type(InvalidResponse.class)
})
public abstract class Response<T> {
}
//Concrete type 1
public class ValidResponse extends Response<T>{
}
//Concrete type 2
public class InvalidResponse extends Response<T>{
}
main {
ObjectMapper mapper = new ObjectMapper();
//Now serialize
ValidResponse response = (ValidResponse)(mapper.readValue(jsonString, Response.class));
//Deserialize
String jsonString = mapper.writeValueAsString(response);
}
Have you tried:
public class AnyResponse {
private String status;
private Error error;
private Contract contract;
private User user;
// And all other possibilities.
}
// ...
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
This should fill in whatever object appears in the JSON and leave the rest null.
You could then fill in a Response with the relevant object.

Categories