Closed deigote closed 5 years ago
At this point I would ask why are you even using Retrofit? It seems like you are gaining almost nothing and it arguably is more in your way. For whatever solution you are trying to achieve it seems like a purpose built solution will be more flexible and probably less code (or equal but more clear code) wouldn't you think?
Retrofit is not meant to solve all use cases and it really sounds like you are fighting to shove a use case into it that is fundamentally opposed to where it currently stands.
@JakeWharton I just exposed one of my use cases :), which I didn't build like that from the beginning - I started writing some interfaces, playing with them a bit by writing some clients and some model classes, then got into the refactor and eliminate duplicated code fun until I ended up with something I was satisfied with: next programmer to use my "framework" will be guided by the compiler/IDE into what she needs to program and which data-types it should deal with
I really like Retrofit so far, they way APIs are exposed as clean, annotated interfaces is very neat. I don't see how trying to "abstract" some common points into a parent interface invalidates Retrofit, but I don't have much experience with it so maybe I'm missing something here.
But just to be clear - I didn't have to struggle to fit my use case into Retrofit. It all came very natural, from the beginning when I just needed to get my data "javized" to now where I'm functional-testing many of my webservices and using these tests to generate their documentation using concordion. My goal is to make the steps for adding new tests as defined as possible - but not defined by some guidelines nobody will read, but by the code itself.
I can't say I've tried many solutions, though - If you honestly think I should walk away from Retrofit I'll do so (would love to hear about alternatives if that's end up being the case).
@JakeWharton needless to say that I'm more than willing to PR update myself if finally you guys decide to consider my request :)
@JakeWharton I have a use for this also: I have a several interfaces which all share the same getEntity method:
public interface ContactsAPI {
@GET(/company/contacts/{entity_id})
public void getEntity(@Path("company_id") Integer companyId,
@Path("entity_id") Integer entityId,
Callback
public interface AccountsAPI {
@GET(/company/accounts/{entity_id})
public void getEntity(@Path("company_id") Integer companyId,
@Path("entity_id") Integer entityId,
Callback
public interface UserAPI {
@GET(/company/users/{entity_id})
public void getEntity(@Path("company_id") Integer companyId,
@Path("entity_id") Integer entityId,
Callback
I want to do something like:
public interface entityAPI
restAdapter.create(ContactAPI.class)
public interface contactAPI extends EntityAPI
restAdapter.create(ContactAPI.class)
Seems like a shame to have to duplicate so much when a lot of it is shared.
same problem than eliasbagley.
Can you add a second parameter on create method to bypass test of interfaces ?
I want to add my 2 cents here as well. Slightly different use case, but I want to split up my API into logical chunks (each of these chunks is implemented by a controller on the server). It would be awesome if I could have a single client API that just shoves all of these back together. Mostly an aesthetic thing, but it would be nice.
Interfaces can only extend from a single other interface so that wouldn't work.
I believe it is permissible in java for an interface to extend multiple interfaces (https://stackoverflow.com/questions/19546357/can-an-interface-extend-multiple-interfaces-in-java).
I am looking for the same thing as briantoth.
I came across this change while doing an upgrade recently. I was using an interface to keep things consistent across multiple APIs, ie: my search endpoints should all look a certain way, and my crud endpoints should look a certain way. It also simplified my test cases, because I could in one test that all my search endpoints accept / require certain params, and for all crud cases I could verify not found behavior for GETs, etc. in one assertion. In the end, I ended up rewriting the tests methods using reflection, just thought I would add my two cents.
Quick patch to add support to disable this "feature" https://github.com/square/retrofit/pull/676 and https://github.com/greeno/retrofit
since the PR #676 was closed, is there hope in that this will be enabled again, at least configurable ? I think this is a subjective opinion enforced while it should be optional, just because you guys think the best way to use retrofit is composition doesn't mean to make it the only way.
Shame that PR #676 is closed :( I need this feature too...
What if we talk not about inheritance vs composition but same interface with different call adapters? With client convenience in mind, I want to provide a factory that can create blocking, async (java8) and reactive (rx) services. Method definitions between them are exactly the same except return values. So at the moment this results into 3 close to being identical interfaces. Would be much better to have parametrized common interface and several extensions per return type: T
, CompletableFuture<T>
and Observable<T>
, for example?
That requires runtime resolution of type parameters which I'm not really eager to get into the business of doing. Can you generate the interfaces from another specification like Swagger or even perhaps use an annotation processor?
On Wed, Nov 2, 2016 at 1:35 AM Eduard Dudar notifications@github.com wrote:
What if we talk not about inheritance vs composition but same interface with different call adapters? With client convenience in mind, I want to provide a factory that can create blocking, async (java8) and reactive (rx) services. Method definitions between them are exactly the same except return values. So at the moment this results into 3 close to being identical interfaces. Would be much better to have parametrized common interface and several extensions per return type: T, CompletableFuture
and Observable , for example? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/square/retrofit/issues/504#issuecomment-257776905, or mute the thread https://github.com/notifications/unsubscribe-auth/AAEEEYeWlfI3_lwgP7nZCIPJ1APhmpvmks5q6CEEgaJpZM4B-pOy .
Understood. Swagger would not be my weapon of choice for sure but custom annotation processor might be the way to go.
@deigote 你可以直接修改源码在本地使用,
第一步: public
第二步:private void eagerlyValidateMethods(Class<?> service) { Platform platform = Platform.get(); for (Method method : service.getMethods()) { if (!platform.isDefaultMethod(method)) { loadServiceMethod(method); } } }
第三步:@HttpApi("http://www.baidu.com/")
public interface IBaiduPushHttpService extends IPushHttpService
第四步:@Autowired
private IPushHttpService
@Test public void oneTest() throws IOException { this.pushHttpService.push().execute(); }
或者Object bean = retrofit.create(serviceClass);,这个serviceClass直接是顶级接口,子接口只是作为模板。
最好的解决办法就是作者,将类的访问控制符号设置为public,然后我们就可以自己扩展一些其他的功能了。(The best solution is the author, which sets the class's access control symbol to public, and then we can extend some of our other functions ourselves.)My English is poor。
Hi there. Great lib, wouldn't know how to live on without it. Solves all usecases in my apps, but one is still unmet. Just pointing this out so I don't get asked why I even use retrofit. :)
But issue remains: #504 !
This is needed, really. Example: `TokenHandler jwtHandler = new JWTTokenHandler(context); Api api = ApiFactory(jwtHandler).createService(clazz);
Observable
.from(entities)
.flatMap(api::save)
.doOnNext(entity -> entity.setSynced(true))
.toList()
.doOnNext(/*save to db, etc*/)
.subscribe(entities -> {}, Throwable::printStackTrace);`
Here and there I read JW favors composition over inheritance and that's why we must write this 13 times, given we have 13 endpoints for 13 entities in 13 service interfaces.
Now I could have a single interface for post requests with 13 methods, each one for a different entity.
` public interface UploadApi {
@POST("users")
Observable
@POST("user-groups")
Observable
@POST("user-photos")
Observable
@POST("user-text-notes")
Observable
@POST("user-actions")
Observable
@POST("user-activities")
Observable
@POST("pois")
Observable
@POST("poi-collections")
Observable
// and so on ... } ` I have seen some projects by now and all of them specify their service interfaces around entites (or dto's or whatya wanna call 'em) like so:
`public interface UserApi {
@GET("users")
Observable<List
@GET("users/{id}")
Observable
@GET("users/{login}")
Observable
@POST("users")
Observable
@POST("users")
Observable
@DELETE("users")
Observable
@DELETE("users/{id}")
Observable
@GET("account")
Observable
I have read somewhere that a library should not be in the way of things but help you to do something more easily. Please reconsider.
Anyway, all the best and thanks for this great lib.
Feature Request
I would like to create a base api service which can be implemented in terms of generics. http://b.android.com/58753 has been resolved and while I understand that composition is the preferred pattern, inheritance can be a very powerful one. Inheritance can help with utilizing large scale consistent apis. I have a rest api with about a hundred endpoints that all expose POST, PATCH, GET and DELETE. As it stands I have two options. 1 - create one massive service api and copy the same blocks of annotated code hundreds of times. 2 - create about a hundred service apis and still copy the same blocks of annotated code hundreds of times. This begs for inheritance. The following code is an example of the inheritance pattern I and others from what I can tell would love to see. This has been an ongoing feature request pattern for about 4 years now.
Note: I have omitted all the @Path, @Header, @HeaderMap, @QueryMap and other annotations that my use case necessitates in order to be concise.
interface BaseService<Response, PostBody> {
@POST("end-point")
fun post(
@Body body : PostBody
) : Call<Response>
}
interface SubService : BaseService<SubResponse, SubPostBody> {
companion object {
fun create(): SubService {
val retrofit: Retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl(SOME_URL)
.build()
return retrofit.create(SubService::class.java)
}
}
}
This has been an ongoing feature request pattern for about 4 years now.
Give it up, move on, copy-Paste it 1000 times and suffer in silence.
FWIW I wanted to do this today to make migrating some legacy code from Retrofit 1 to Retrofit 2 easier.
@co5dt I gave up, I moved on, I copy and pasted it 1000 times and the suffering continues...
To be honest, i think this is very nice feature. If you build application with multiple flavors it is very common to have structure like: main -> BaseUserInterface client1 -> UserInterface extends BaseUserInterface (adds some new calls) client2 -> UserInterface extends BaseUserInterface (adds another set of new calls) client3 -> UserInterface extends BaseUserInterface (doesn't add any new calls)
This is very clean approach. And not being able to extend interface require lots of work where code is much less readable to achieve same behaviour
Why wouldn't you just split those up into multiple interfaces? Clearly they have semantic meaning based on the variant so codify that meaning into the type system.
On Tue, Apr 24, 2018 at 10:47 AM Bartosz Wyspiański < notifications@github.com> wrote:
To be honest, i think this is very nice feature. If you build application with multiple flavors it is very common to have structure like: main -> BaseUserInterface client1 -> UserInterface extends BaseUserInterface (adds some new calls) client2 -> UserInterface extends BaseUserInterface (adds another set of new calls) client3 -> UserInterface extends BaseUserInterface (doesn't add any new calls)
This is very clean approach. And not being able to extend interface require lots of work where code is much less readable to achieve same behaviour
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/square/retrofit/issues/504#issuecomment-383960084, or mute the thread https://github.com/notifications/unsubscribe-auth/AAEEEatmTNeiQkwKbJmPz5--v5gDu1Gnks5trzrtgaJpZM4B-pOy .
My problem is that I must split them up into multiple interfaces that cannot take advantage of basic principles of inheritance or generics. Each of my ApiServices is generally the same, but they need to be different in three ways that could be solved by inheritance and generics. 1) The string required by each APiService's @POST, @PATCH, @GET annotation is different. Solved by abstraction and inheritance. 2) The object type required by the @Body annotation needs to be type safe and different for each ApiService. Solved by generics. 3) The Call return type of ApiService method needs to also be type safe and different for each. Again solved by generics.
If I had one Api service, implemented in terms of generics, and open to inheritance then each one of my plethora of ApiServices could go from 100 lines of code to 15 lines of code, needing only to specify its api endpoint through abstraction, and its Body and Call object type through generics. It could pivot from a twisted ugly maintenance issue to a beautifully crafted example of pattern recognition, abstraction and inheritance implemented through generics.
That's really upsetting, I liked retrofit a lot but now I just cannot understand why disagreeing with everyone just for the sake of disagreement! I've implemented a generic proxy pipeline for CRUD operations in downstream dependencies, and provided a generic retrofit interface which will be extended by potential proxied service clients, and while doing integration test, I found this non-sense error!
Now, for each new (to be proxied) downstream dependency that we want to integrate with our backend API layer, we have to duplicate exact same code just because inheritance/polymorphism is not allowed!!
So either copy/paste logic/tests, or search for different lib & do the same infrastructure we did built on top of retrofit again.
I really hope that you guys reconsider hearing all of these voices!
Hi all!
I faced with the case in which interface inheritance will be useful. I have a library with interface like that:
interface AuthService {
void authorize(String login, String password);
}
And 2 apps uses this lib: app1:
interface App1AuthService extends AuthService {
@GET("/auth")
@Override
void authorize(@Query("login") String login, @Query("password") String password);
}
app2:
interface App2AuthService extends AuthService {
@GET("/auth/special")
@Override
void authorize(@Query("login") String login, @Query("password") String password);
}
So now I would like to inject correct implementation of AuthService
into lib but I can't due to retrofit restriction =(
It's been almost a year now since I follow this thread and every time I get an update, I just shake my head a little.
Let's fork this awesome library to overcome the authors s....y attitude.
First thing I'll do once I'm back from vacation.
Retrofit tries to make REST APIs simple. We do our best to make APIs that are easy to call, configure, and test.
We have made several decisions in Retrofit where we’ve deliberately left something out in order to focus and simplify the library. Some of these decisions may seem obvious, but often they required a lot of research.
Just because we disagree on this particular issue does not mean we’re shitty or lazy. Suggesting so is discouraging.
Also there's that whole thing where it is broken on certain versions of Android…
@JakeWharton you mention that there are versions of Android where this wouldn't work. Could you specify which versions of Android that it doesn't work on? I came across this issue just recently, because I'm working on a whitelabel solution of an app where we use different flavours and different flavours sometimes mean different endpoints, but most endpoints are the same, so for me inheritance is the obvious solution for this.
Consider this:
I have flavour A that uses endpoint:
interface Api {
String BASE_URL = "http://myurl.com/";
@GET("{link}")
LiveData<Data> getItem(@Path(value = "link", encoded = true) String link);
@GET("{link}")
LiveData<Response> getItems(@Path(value = "link", encoded = true) String link);
}
Now I also have flavour B, that uses the same API, except that it also has an extra method:
@GET("{link}")
LiveData<Response> getItems(@Path(value = "link", encoded = true) String link);
Now for brevity and for the sake of the next developer and also for the sake of not duplicating code, I would really love to be able to have a BaseApi
interface that I could just inherit from, but obviously I can't because Retrofit doesn't allow it.
I get that you guys favor composition over inheritance and I completely agree that you should use composition as much as possible, but there is a reason why Java still supports inheritance as well as composition.
Retrofit is such an integrated part of Android these days and I'm even surprised why it hasn't been moved into Jetpack or something similar, but maybe Google is thinking of making this move at some point in the near future (would be nice 🙂). While you guys obviously has the final say in this matter I really hope you could reconsider opening up for this - isn't it pretty clear that the community wants this? 🙂
I'll keep copy-pasting code around for now, but it would be amazing to have this sorted at some point.
If the final say has been said in this matter then you should close this issue so we can all move on.
This is a super-frustrating restriction. And it was fun to find out at runtime!
I have a similar use case as @devindi where I need to choose the right API implementation dynamically for AWS ECS metadata because they are the same except for the URLs.
String ecsV3Uri = System.getenv("ECS_CONTAINER_METADATA_URI");
Retrofit.Builder retrofitBuilder = new Retrofit.Builder()
.addConverterFactory(JacksonConverterFactory.create(mapper))
.client(httpClient);
if (StringUtils.isEmpty(ecsV3Uri)) {
// this is a v2 API (AWS Fargate ECS)
retrofitBuilder
.baseUrl("http://169.254.170.2/v2/");
return retrofitBuilder.build().create(Ecsv2MetadataApi.class);
}
else {
// this is a v3 API (EC2-based ECS)
retrofitBuilder
.baseUrl(ecsV3Uri + '/');
return retrofitBuilder.build().create(Ecsv3MetadataApi.class);
}
It's likely that 2.7.0 will remove the restriction by removing support for Android versions where proxying an interface with a supertype does not work (which is the source of the current limitation).
On Wed, Jun 5, 2019 at 11:54 AM Jason Axley notifications@github.com wrote:
This is a super-frustrating restriction. And it was fun to find out at runtime!
I have a similar use case as @devindi https://github.com/devindi where I need to choose the right API implementation dynamically for AWS ECS metadata because they are the same except for the URLs.
String ecsV3Uri = System.getenv("ECS_CONTAINER_METADATA_URI"); Retrofit.Builder retrofitBuilder = new Retrofit.Builder() .addConverterFactory(JacksonConverterFactory.create(mapper)) .client(httpClient); if (StringUtils.isEmpty(ecsV3Uri)) { // this is a v2 API (AWS Fargate ECS) retrofitBuilder .baseUrl("http://169.254.170.2/v2/"); return retrofitBuilder.build().create(Ecsv2MetadataApi.class); } else { // this is a v3 API (EC2-based ECS) retrofitBuilder .baseUrl(ecsV3Uri + '/'); return retrofitBuilder.build().create(Ecsv3MetadataApi.class); }
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/square/retrofit/issues/504?email_source=notifications&email_token=AAAQIEO775TJY47UKKKOH53PY7OTRA5CNFSM4AP2SOZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXAFKFY#issuecomment-499143959, or mute the thread https://github.com/notifications/unsubscribe-auth/AAAQIEL3DWPKIB33SNKNO7DPY7OTRANCNFSM4AP2SOZA .
That would be amazing. would it be possible to re-open https://github.com/square/retrofit/pull/676 to enable this feature as an option in the near future? I just stumbled upon this same issue in our project and was a bit jarred by the failure at runtime.
This issue is still present in the released 2.6.1 and 2.6.2 versions of retrofit. Somehow it appears that the commit was not included in the release.
That's correct. Those releases were made from the 2.6.0 branch.
On Thu, Oct 31, 2019, 1:34 PM shiSHARK notifications@github.com wrote:
This issue is still present in the released 2.6.1 and 2.6.2 versions of retrofit. Somehow it appears that the commit was not included in the release.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/square/retrofit/issues/504?email_source=notifications&email_token=AAAQIEOJGUV2XIT2RRWO6QLQRMJJFA5CNFSM4AP2SOZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECYT5QY#issuecomment-548486851, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAQIEND2LI7XBJ67P2SI2LQRMJJFANCNFSM4AP2SOZA .
That's correct. Those releases were made from the 2.6.0 branch. … On Thu, Oct 31, 2019, 1:34 PM shiSHARK @.***> wrote: This issue is still present in the released 2.6.1 and 2.6.2 versions of retrofit. Somehow it appears that the commit was not included in the release. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#504?email_source=notifications&email_token=AAAQIEOJGUV2XIT2RRWO6QLQRMJJFA5CNFSM4AP2SOZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECYT5QY#issuecomment-548486851>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAQIEND2LI7XBJ67P2SI2LQRMJJFANCNFSM4AP2SOZA .
So how can we get that feature ? on which version ?
It is not released
On Thu, Nov 21, 2019 at 9:48 AM HebaMohamed notifications@github.com wrote:
That's correct. Those releases were made from the 2.6.0 branch. … <#m5575032184380938997> On Thu, Oct 31, 2019, 1:34 PM shiSHARK @.***> wrote: This issue is still present in the released 2.6.1 and 2.6.2 versions of retrofit. Somehow it appears that the commit was not included in the release. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#504 https://github.com/square/retrofit/issues/504?email_source=notifications&email_token=AAAQIEOJGUV2XIT2RRWO6QLQRMJJFA5CNFSM4AP2SOZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECYT5QY#issuecomment-548486851>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAQIEND2LI7XBJ67P2SI2LQRMJJFANCNFSM4AP2SOZA .
So how can we get that feature ? on which version ?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/square/retrofit/issues/504?email_source=notifications&email_token=AAAQIEOVR3E4VORDTEFEOZLQU2NVXA5CNFSM4AP2SOZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEE2PDKA#issuecomment-557117864, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAQIEKHCNM73HXXC7BKF4LQU2NVXANCNFSM4AP2SOZA .
Afaik now in 2.9.0 in 2024, this limitation is no more there ? @JakeWharton
The comment:
in:
https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit/Utils.java
Sounds rather arbitrary. I think it should be configurable at the very least.
I for example have something very similar to the following.
A base response class:
and an interface that defines how my API needs to be accessed:
I latter extend that interface with some specific API like:
being UsersResponse an extension of GenericResponse. With something like that, I can implement an abstract client like:
That client can work with any of my APIs, avoiding code duplication while having typing enforced by the compiler using generics. It can deal with any obtainData connector and with the common parts of every response. When implemented by a concrete class, thanks to the use of generics and inheritance, the data is transformed to the proper specific response class and when using an IDE with generics support (any, I guess :) is very convenient to use every method with the signature automatically transformed to the concrete classes declared by the implementor class.
I understand that composition is better for many things, but I don't see how to work with a generic connector the way I'm doing it, being able to assume that every connector will have an obtainData with the given parameters and having that enforced by the compiler.
I'm not saying is not possible - I'm saying I don't know how, and the restriction I'm finding is, in my opinion, quite poorly explained.
I think something whose reason to be is something as arguable as "composition is the only possible, good way" should be configurable at least.