Slide 1

Slide 1 text

Retrofit2ͷ࢓૊Έ Androidݚڀ&ൃදձ #2 2019/10/26 ʙΦϦδφϧConverterΛ࡞ͬͯΈΔʙ

Slide 2

Slide 2 text

• Տຊ ହ޹(͔Θ΋ͱ ΍͔ͨ͢) • ॴଐɿגࣜձࣾ tech vein 
 (େࡕࢢதԝ۠ຊொ) • ϞόΠϧΞϓϦΤϯδχΞ
 (AndroidଟΊɺiOS΋ŧŔŕŪũƄŝſ) • GitHub: kwmt ɺtwitter: kwmt27 • Google I/O2018 ॳࢀՃ • ෱Ԭग़਎ • ΫϥϑτϏʔϧ޷͖ 2 ࣗݾ঺հ

Slide 3

Slide 3 text

• Squareࣾ੡ • JavaϥΠϒϥϦ • HTTPΫϥΠΞϯτ • Android or JavaͰॻ͔ΕͨαʔόʔͰಈ͘ Retrofitͱ͸

Slide 4

Slide 4 text

interface ApiService { } RetrofitͷΞΠσΞ

Slide 5

Slide 5 text

interface ApiService { } RetrofitͷΞΠσΞ class HttpGitHubService : ApiService { // … }

Slide 6

Slide 6 text

interface ApiService { } RetrofitͷΞΠσΞ

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

implementation 'com.squareup.retrofit2:retrofit:2.6.2'

Slide 9

Slide 9 text

interface GitHubService { fun listRepos(user: String): List }

Slide 10

Slide 10 text

interface GitHubService { fun listRepos(user: String): List } data class Repo(val name: String)

Slide 11

Slide 11 text

interface GitHubService { fun listRepos(user: String): List }

Slide 12

Slide 12 text

interface GitHubService { @GET("users/{user}/repos") fun listRepos(user: String): List }

Slide 13

Slide 13 text

interface GitHubService { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): List }

Slide 14

Slide 14 text

interface GitHubService { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Call> }

Slide 15

Slide 15 text

val retrofit: Retrofit = Retrofit.Builder() .baseUrl("https://api.github.com/") .build()

Slide 16

Slide 16 text

val retrofit: Retrofit = Retrofit.Builder() .baseUrl("https://api.github.com/") .build() val gitHubService: GitHubService 
 = retrofit.create(GitHubService::class.java)

Slide 17

Slide 17 text

val repos: Call> = gitHubService.listRepos("kwmt")

Slide 18

Slide 18 text

val repos: Call> = gitHubService.listRepos("kwmt") repos.enqueue(object : retrofit2.Callback> { override fun onFailure(call: Call>, t: Throwable) { Log.d("GitHubSample", t.message) } override fun onResponse( call: Call>, response: Response>) { Log.d("GitHubSample", response.toString()) } })

Slide 19

Slide 19 text

Caused by: java.lang.IllegalArgumentException: 
 Could not locate ResponseBody converter for
 java.util.List.

Slide 20

Slide 20 text

Ϋϥογϡ͠·͢ɻ Caused by: java.lang.IllegalArgumentException: 
 Could not locate ResponseBody converter for
 java.util.List.

Slide 21

Slide 21 text

Ϋϥογϡ͠·͢ɻ Caused by: java.lang.IllegalArgumentException: 
 Could not locate ResponseBody converter for
 java.util.List. ͳΜ͔ConverterབྷΈͰౖΒΕͯΔ༧૝͸͖ͭ·͕͢ɺ

Slide 22

Slide 22 text

Ϋϥογϡ͠·͢ɻ Caused by: java.lang.IllegalArgumentException: 
 Could not locate ResponseBody converter for
 java.util.List. val repos: Call> = gitHubService.listRepos("kwmt") ͳΜ͔ConverterབྷΈͰౖΒΕͯΔ༧૝͸͖ͭ·͕͢ɺ

Slide 23

Slide 23 text

Ϋϥογϡ͠·͢ɻ Caused by: java.lang.IllegalArgumentException: 
 Could not locate ResponseBody converter for
 java.util.List. val repos: Call> = gitHubService.listRepos("kwmt") ͜͜ͰΫϥογϡ͍ͯ͠ΔΑ͏ͳͷͰɺਂ͘ݟͯΈ·͢ɻ ͳΜ͔ConverterབྷΈͰౖΒΕͯΔ༧૝͸͖ͭ·͕͢ɺ

Slide 24

Slide 24 text

retrofit.create(GitHubService::class.java) public T create(final Class service) { ɹ// লུ return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }

Slide 25

Slide 25 text

retrofit.create(GitHubService::class.java) public T create(final Class service) { ɹ// লུ return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }

Slide 26

Slide 26 text

retrofit.create(GitHubService::class.java) public T create(final Class service) { ɹ// লུ return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }

Slide 27

Slide 27 text

if (annotation instanceof DELETE) { parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false); } else if (annotation instanceof GET) { parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); } else if (annotation instanceof HEAD) { parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false); } else if (annotation instanceof PATCH) { parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true); } else if (annotation instanceof POST) { parseHttpMethodAndPath("POST", ((POST) annotation).value(), true); } else if (annotation instanceof PUT) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);

Slide 28

Slide 28 text

if (annotation instanceof DELETE) { parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false); } else if (annotation instanceof GET) { parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); } else if (annotation instanceof HEAD) { parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false); } else if (annotation instanceof PATCH) { parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true); } else if (annotation instanceof POST) { parseHttpMethodAndPath("POST", ((POST) annotation).value(), true); } else if (annotation instanceof PUT) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);

Slide 29

Slide 29 text

if (annotation instanceof DELETE) { parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false); } else if (annotation instanceof GET) { parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); } else if (annotation instanceof HEAD) { parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false); } else if (annotation instanceof PATCH) { parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true); } else if (annotation instanceof POST) { parseHttpMethodAndPath("POST", ((POST) annotation).value(), true); } else if (annotation instanceof PUT) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);

Slide 30

Slide 30 text

if (annotation instanceof DELETE) { parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false); } else if (annotation instanceof GET) { parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); } else if (annotation instanceof HEAD) { parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false); } else if (annotation instanceof PATCH) { parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true); } else if (annotation instanceof POST) { parseHttpMethodAndPath("POST", ((POST) annotation).value(), true); } else if (annotation instanceof PUT) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);

Slide 31

Slide 31 text

HTTPϝιουΛ஍ಓʹύʔε if (annotation instanceof DELETE) { parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false); } else if (annotation instanceof GET) { parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); } else if (annotation instanceof HEAD) { parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false); } else if (annotation instanceof PATCH) { parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true); } else if (annotation instanceof POST) { parseHttpMethodAndPath("POST", ((POST) annotation).value(), true); } else if (annotation instanceof PUT) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);

Slide 32

Slide 32 text

HTTPϝιου͕ࢦఆ͞Εͯͳ͚Ε͹ɺ
 IllegalArgumentException͕εϩʔ͞Ε·͢ if (httpMethod == null) { throw methodError(method, 
 "HTTP method annotation is required (e.g., @GET, @POST, etc.)."); }

Slide 33

Slide 33 text

body͕ແͯ͘(PATCH,POST,PUTҎ֎) @Multipart΍@FormUrlEncodedΛࢦఆͨ͠৔߹ɺ
 IllegalArgumentException͕εϩʔ͞Ε·͢ if (!hasBody) { if (isMultipart) { throw methodError(method, "Multipart can only be specified on HTTP methods with request body (e.g., @POST)."); } if (isFormEncoded) { throw methodError(method, "FormUrlEncoded can only be specified on HTTP methods with " + "request body (e.g., @POST)."); } }

Slide 34

Slide 34 text

int start = converterFactories.indexOf(skipPast) + 1; for (int i = start, count = converterFactories.size(); i < count; i++) { Converter converter = converterFactories.get(i) .responseBodyConverter(type, annotations, this); if (converter != null) { return (Converter) converter; } } StringBuilder builder = new StringBuilder("Could not locate ResponseBody converter for ") .append(type) .append(“.\n"); // লུ throw new IllegalArgumentException(builder.toString());

Slide 35

Slide 35 text

int start = converterFactories.indexOf(skipPast) + 1; for (int i = start, count = converterFactories.size(); i < count; i++) { Converter converter = converterFactories.get(i) .responseBodyConverter(type, annotations, this); if (converter != null) { return (Converter) converter; } } StringBuilder builder = new StringBuilder("Could not locate ResponseBody converter for ") .append(type) .append(“.\n"); // লུ throw new IllegalArgumentException(builder.toString());

Slide 36

Slide 36 text

Converter converter = converterFactories.get(i) .responseBodyConverter(type, annotations, this); if (converter != null) { return (Converter) converter; }

Slide 37

Slide 37 text

Converter converter = converterFactories.get(i) .responseBodyConverter(type, annotations, this); if (converter != null) { return (Converter) converter; }

Slide 38

Slide 38 text

Converter converter = converterFactories.get(i) .responseBodyConverter(type, annotations, this); if (converter != null) { return (Converter) converter; }

Slide 39

Slide 39 text

Converter converter = converterFactories.get(i) .responseBodyConverter(type, annotations, this); if (converter != null) { return (Converter) converter; }

Slide 40

Slide 40 text

Converter converter = converterFactories.get(i) .responseBodyConverter(type, annotations, this); if (converter != null) { return (Converter) converter; } responseBodyConverterϝιου͕ nullΛฦ͞ͳ͍Α͏ͳɺConverterFactory͕ converterFactoriesʹηοτͰ͖Ε͹ྑ͍ ͜ͱ͕Θ͔ͬͨɻ

Slide 41

Slide 41 text

class MyConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { return MyResponseBodyConverter() } class MyResponseBodyConverter : Converter { override fun convert(value: ResponseBody): String? { return "test" } } }

Slide 42

Slide 42 text

class MyConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { return MyResponseBodyConverter() } class MyResponseBodyConverter : Converter { override fun convert(value: ResponseBody): String? { return "test" } } }

Slide 43

Slide 43 text

class MyConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { return MyResponseBodyConverter() } class MyResponseBodyConverter : Converter { override fun convert(value: ResponseBody): String? { return "test" } } }

Slide 44

Slide 44 text

class MyConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { return MyResponseBodyConverter() } class MyResponseBodyConverter : Converter { override fun convert(value: ResponseBody): String? { return "test" } } }

Slide 45

Slide 45 text

class MyConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { return MyResponseBodyConverter() } class MyResponseBodyConverter : Converter { override fun convert(value: ResponseBody): String? { return "test" } } }

Slide 46

Slide 46 text

class MyConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { return MyResponseBodyConverter() } class MyResponseBodyConverter : Converter { override fun convert(value: ResponseBody): String? { return "test" } } }

Slide 47

Slide 47 text

class MyConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { return MyResponseBodyConverter() } class MyResponseBodyConverter : Converter { override fun convert(value: ResponseBody): String? { return "test" } } }

Slide 48

Slide 48 text

val retrofit = Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(MyConverterFactory()) .build()

Slide 49

Slide 49 text

repos.enqueue(object : retrofit2.Callback> { override fun onFailure(call: Call>, t: Throwable) { Log.d("GitHubSample", t.message) } override fun onResponse( call: Call>, response: Response>) { Log.d("GitHubSample", response.toString()) } })

Slide 50

Slide 50 text

repos.enqueue(object : retrofit2.Callback> { override fun onFailure(call: Call>, t: Throwable) { Log.d("GitHubSample", t.message) } override fun onResponse( call: Call>, response: Response>) { Log.d("GitHubSample", response.toString()) } }) Ϋϥογϡ͸͠ͳ͘ͳ͕ͬͨɺ ΄͍͠ͷ͸ListͰ StringͰ͸ͳ͍

Slide 51

Slide 51 text

Retrofit͕ConverterΛ༻ҙ

Slide 52

Slide 52 text

ConverterͰ ResponseBody ͔Β Listʹม׵ https://github.com/Kotlin/kotlinx.serialization

Slide 53

Slide 53 text

data class Repo(val name: String) ConverterͰ ResponseBody ͔Β Listʹม׵

Slide 54

Slide 54 text

import kotlinx.serialization.Serializable @Serializable data class Repo(val name: String) ConverterͰ ResponseBody ͔Β Listʹม׵

Slide 55

Slide 55 text

class MyResponseBodyConverter : Converter { override fun convert(value: ResponseBody): String? { return "test" } } ConverterͰ ResponseBody ͔Β Listʹม׵

Slide 56

Slide 56 text

class MyResponseBodyConverter : Converter> { override fun convert(value: ResponseBody): List? { // ResponseBody͔ΒString΁ͷม׵෦෼͸লུ val json = Json { strictMode = false } return json.parse(Repo.serializer().list, content)) } } class MyResponseBodyConverter : Converter { override fun convert(value: ResponseBody): String? { return "test" } } ConverterͰ ResponseBody ͔Β Listʹม׵

Slide 57

Slide 57 text

ListΛऔಘ ConverterͰ ResponseBody ͔Β Listʹม׵

Slide 58

Slide 58 text

class MyConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { return MyResponseBodyConverter() } }

Slide 59

Slide 59 text

class MyConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { return MyResponseBodyConverter() } override fun requestBodyConverter( type: Type, parameterAnnotations: Array, methodAnnotations: Array, retrofit: Retrofit ): Converter<*, RequestBody>? { return MyRequestBodyConverter() } }

Slide 60

Slide 60 text

• Retrofit͸ΞϊςʔγϣϯΛ࢖ͬͯHTTPϝιου΍ύε Λࢦఆ͢Δ • Retrofit͸Proxy.newProxyInstanceΛ࢖ͬͯΞϊςʔ γϣϯΛύʔε͍ͯ͠Δ • RepoΫϥεͷΑ͏ͳಠࣗͷܕʹม׵͢Δʹ͸ɺ Converter͕ඞཁ ·ͱΊ

Slide 61

Slide 61 text

࠷ޙʹ(1) https://github.com/kwmt/RetrofitSample

Slide 62

Slide 62 text

࠷ޙʹ(2) https://github.com/JakeWharton/retrofit2-kotlinx-serialization-converter

Slide 63

Slide 63 text

࠷ޙʹ(3) DroidKaigi2020ͷCfSΛ ʮৄࡉRetrofit2ʯͱ͍͏λΠτϧͰग़͠·ͨ͠ʂ

Slide 64

Slide 64 text

͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ @kwmt27