{"id":31126,"date":"2019-05-15T22:54:30","date_gmt":"2019-05-15T13:54:30","guid":{"rendered":"https:\/\/jirak.net\/wp\/aws-amplify%eb%a5%bc-%ec%9d%b4%ec%9a%a9%ed%95%9c-android-%ec%95%b1-%ea%b0%9c%eb%b0%9c-%ec%8b%a4%ec%8a%b5-2%eb%b6%80\/"},"modified":"2019-05-15T23:34:25","modified_gmt":"2019-05-15T14:34:25","slug":"aws-amplify%eb%a5%bc-%ec%9d%b4%ec%9a%a9%ed%95%9c-android-%ec%95%b1-%ea%b0%9c%eb%b0%9c-%ec%8b%a4%ec%8a%b5-2%eb%b6%80","status":"publish","type":"post","link":"https:\/\/jirak.net\/wp\/aws-amplify%eb%a5%bc-%ec%9d%b4%ec%9a%a9%ed%95%9c-android-%ec%95%b1-%ea%b0%9c%eb%b0%9c-%ec%8b%a4%ec%8a%b5-2%eb%b6%80\/","title":{"rendered":"AWS Amplify\ub97c \uc774\uc6a9\ud55c Android \uc571 \uac1c\ubc1c \uc2e4\uc2b5 \u2013 2\ubd80"},"content":{"rendered":"<p>AWS Amplify\ub97c \uc774\uc6a9\ud55c Android \uc571 \uac1c\ubc1c \uc2e4\uc2b5 \u2013 2\ubd80<br \/>\n<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jirak.net\/wp\/wp-content\/uploads\/2019\/05\/virtualdevices.png\" width=\"804\" height=\"449\"><\/p>\n<p><em>\uc774 \uae00\uc740 <a href=\"https:\/\/aws-amplify.github.io\/\">AWS Amplify \uac1c\ubc1c \ub3c4\uad6c<\/a>\ub97c \uc0ac\uc6a9\ud558\uc5ec AWS \ud074\ub77c\uc6b0\ub4dc \uae30\ubc18 Android \ubaa8\ubc14\uc77c \uc571\uc744 \uc81c\uc791\ud558\ub294 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc2e4\uc2b5 \uc2dc\ub9ac\uc988\uc785\ub2c8\ub2e4.<\/em><\/p>\n<p>\uc774 \uae00\uc5d0\uc11c\ub294 <a href=\"https:\/\/aws.amazon.com\/ko\/blogs\/korea\/building-an-android-app-with-aws-amplify-part-1\/\">1\ubd80<\/a>\uc5d0 \uc774\uc5b4 Android \uc571\uc5d0 \uace0\uae09 \uae30\ub2a5\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4. \ub2e4\ub8e8\ub294 \ub0b4\uc6a9\uc740 \ub2e4\uc74c\uacfc \uac19\uc2b5\ub2c8\ub2e4.<\/p>\n<ul>\n<li>\uae30\ub2a5 \uc5c5\ub370\uc774\ud2b8 : <a href=\"https:\/\/aws.amazon.com\/appsync\/\">AWS AppSync API<\/a> \uc624\ud504\ub77c\uc778 \uc9c0\uc6d0<\/li>\n<li>\ub370\uc774\ud130 \ubcc0\uacbd(\ubcc0\ud615)\uc5d0 \ub300\ud55c \uad6c\ub3c5 \uc0ac\uc6a9<\/li>\n<li><a href=\"https:\/\/aws.amazon.com\/s3\/\">Amazon S3<\/a>\ub97c \ud1b5\ud55c \uac1d\uccb4 \uc2a4\ud1a0\ub9ac\uc9c0 \ud65c\uc131\ud654<\/li>\n<\/ul>\n<h2>\uc0ac\uc804 \uc870\uac74<\/h2>\n<p>Android \ud504\ub85c\uc81d\ud2b8\ub97c \uc791\uc131\ud558\ub824\uba74 \uc6cc\ud06c \uc2a4\ud14c\uc774\uc158\uc5d0 <a href=\"https:\/\/www.oracle.com\/technetwork\/java\/javase\/downloads\/jdk8-downloads-2133151.html\">Java JDK<\/a>\uac00 \uc124\uce58\ub418\uc5b4 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4. <a href=\"https:\/\/developer.android.com\/studio\/\">Android Studio<\/a>\ub97c \ub2e4\uc6b4\ub85c\ub4dc\ud558\uc5ec \uc124\uce58\ud558\uace0 Android SDK \uad00\ub9ac\uc790\uc5d0\uc11c Android 6.0 SDK(API \ub808\ubca8 23 \uc774\uc0c1)\ub97c \ub2e4\uc6b4\ub85c\ub4dc\ud569\ub2c8\ub2e4.<\/p>\n<p>\ub610\ud55c \uc5d0\ubbac\ub808\uc774\ud130 \uc774\ubbf8\uc9c0\ub3c4 \ub2e4\uc6b4\ub85c\ub4dc\ud569\ub2c8\ub2e4. \uc774\ub97c \uc704\ud574 Android Studio\uc5d0\uc11c <strong>AVD Manager<\/strong>\ub97c \uc120\ud0dd\ud574\uc57c \ud569\ub2c8\ub2e4. <strong>+ Create Virtual Device<\/strong>\ub97c \uc120\ud0dd\ud558\uace0 \uc9c0\uce68\uc5d0 \ub530\ub77c \uc124\uce58\ub97c \uc644\ub8cc\ud569\ub2c8\ub2e4.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-2034 size-full\" src=\"https:\/\/d2908q01vomqb2.cloudfront.net\/0a57cb53ba59c46fc4b692527a38a87c78d84028\/2018\/11\/14\/virtualdevices.png\" alt=\"\" width=\"804\" height=\"449\" \/><\/p>\n<h2><strong>1 \ubd80\uc5d0\uc11c \uacc4\uc18d<\/strong><\/h2>\n<p><a href=\"https:\/\/aws.amazon.com\/ko\/blogs\/korea\/building-an-android-app-with-aws-amplify-part-1\/\">1\ubd80<\/a>\uc5d0\uc11c \uc6b0\ub9ac\ub294 \uc560\uc644 \ub3d9\ubb3c\uc758 \ubaa9\ub85d\uc744 \ud45c\uc2dc\ud558\uace0 \uc0c8\ub85c\uc6b4 \uc560\uc644 \ub3d9\ubb3c\uc744 \ucd94\uac00\ud560 \uc218 \uc788\ub294 Android \uc571\uc744 \ub9cc\ub4e4\uc5c8\uc2b5\ub2c8\ub2e4. \uc0c8 \uc560\uc644 \ub3d9\ubb3c\uc744 \ucd94\uac00\ud558\uba74 \uc571\uc740 \ub2e4\uc74c\uacfc \uac19\uc740 \ud615\ud0dc\uac00 \ub429\ub2c8\ub2e4.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2040\" src=\"https:\/\/d2908q01vomqb2.cloudfront.net\/0a57cb53ba59c46fc4b692527a38a87c78d84028\/2018\/11\/14\/PetAdded.png\" alt=\"\" width=\"250\" height=\"444\" \/><\/p>\n<h2><strong>\ucd5c\uc801 \uc5c5\ub370\uc774\ud2b8 \ubc0f <\/strong><strong>\uc624\ud504\ub77c\uc778 \uc9c0\uc6d0<\/strong><\/h2>\n<p>\ucd5c\uc801 \uc5c5\ub370\uc774\ud2b8 \uae30\ub2a5\uc744 \ud1b5\ud574 \ubc18\uc751\uc131\uc774 \ub6f0\uc5b4\ub09c \ucd5c\uc885 \uc0ac\uc6a9\uc790 \ud658\uacbd\uc744 \uc81c\uacf5\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc11c\ubc84\uac00 \uacb0\uad6d \uc6b0\ub9ac\uac00 \uae30\ub300\ud558\ub294 \ub370\uc774\ud130\ub97c \ubc18\ud658\ud558\ub294 \uac83\ucc98\ub7fc \ub3d9\uc791\ud558\ub3c4\ub85d UI\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4. \uc5c5\ub370\uc774\ud2b8\uac00 \uc131\uacf5\uc801\uc774\ub77c\ub294 \uac83\uc740 \ub099\uad00\uc801\uc785\ub2c8\ub2e4.<\/p>\n<p>\uc774 \uc139\uc158\uc5d0\uc11c\ub294 \ubcc0\ud615 \uc774\ud6c4\uc5d0 \uba54\ubaa8\ub9ac\uc5d0 \ubc18\ud658\ub420 \uac83\uc73c\ub85c \uc608\uc0c1\ub418\ub294 \ub370\uc774\ud130\ub97c \ub9cc\ub4e4\uace0 Android \ub514\ubc14\uc774\uc2a4\uac00 \uad00\ub9ac\ud558\ub294 \uc601\uad6c SQL \uc800\uc7a5\uc18c\uc5d0 \uc791\uc131\ud569\ub2c8\ub2e4. \uadf8\ub7f0 \ub2e4\uc74c, \uc11c\ubc84 \uc5c5\ub370\uc774\ud2b8\uac00 \ubc18\ud658\ub418\uba74 SDK\uac00 \ub370\uc774\ud130\ub97c \ud1b5\ud569\ud569\ub2c8\ub2e4.<\/p>\n<p>\uc774 \uc811\uadfc\ubc95\uc740 \ub370\uc774\ud130 \uc218\uc815\uc744 \uc2dc\ub3c4\ud558\ub294 \ub3d9\uc548 \uc778\ud130\ub137 \uc5f0\uacb0\uc774 \ub04a\uae30\ub294 \uc2dc\ub098\ub9ac\uc624\uc5d0 \ub9e4\uc6b0 \uc801\ud569\ud569\ub2c8\ub2e4. AWS AppSync SDK\ub294 \uc571\uc774 \uc628\ub77c\uc778 \uc0c1\ud0dc\uac00 \ub418\uba74 \uc790\ub3d9\uc73c\ub85c \ub2e4\uc2dc \uc5f0\uacb0\ud558\uc5ec \ubcc0\ud615\uc744 \uc804\uc1a1\ud569\ub2c8\ub2e4.<\/p>\n<p>\uc774\uc81c \uc2e4\uc2b5\ud574 \ubd05\uc2dc\ub2e4. <code>AddPetActivity.java<\/code>\ub97c \uc5f4\uace0 <code>save()<\/code>\uac00 \ub05d\ub0a0 \ub54c \uc624\ud504\ub77c\uc778 \uc9c0\uc6d0\uc744 \ucd94\uac00\ud558\uc2ed\uc2dc\uc624.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private void save() {\n      \/\/ ... Other code ...\n\n      ClientFactory.appSyncClient().mutate(addPetMutation).\n              refetchQueries(ListPetsQuery.builder().build()).\n              enqueue(mutateCallback);\n\n      \/\/ Enables offline support via an optimistic update\n      \/\/ Add to event list while offline or before request returns\n      addPetOffline(input);\n}<\/code><\/pre>\n<\/div>\n<p>\uc774\uc81c <code>addPetOffline<\/code> \uba54\uc11c\ub4dc\ub97c \ucd94\uac00\ud574 \ubd05\uc2dc\ub2e4. \uc6b0\ub9ac\ub294 \ub85c\uceec \uce90\uc2dc\uc5d0 \uae30\ub85d\ud55c \ud6c4\uc5d0 \uc5f0\uacb0\uc131\uc744 \ud655\uc778\ud558\uace0 \ucd94\uac00 \uc791\uc5c5\uc774 \uc131\uacf5\uc801\uc778 \uac83\ucc98\ub7fc \ud65c\ub3d9\uc744 \uc885\ub8cc\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private void addPetOffline(CreatePetInput input) {\n\n  final CreatePetMutation.CreatePet expected =\n          new CreatePetMutation.CreatePet(\n                  &quot;Pet&quot;,\n                  UUID.randomUUID().toString(),\n                  input.name(),\n                  input.description());\n                  \n\n  final AWSAppSyncClient awsAppSyncClient = ClientFactory.appSyncClient();\n  final ListPetsQuery listEventsQuery = ListPetsQuery.builder().build();\n  \n\n  awsAppSyncClient.query(listEventsQuery)\n          .responseFetcher(AppSyncResponseFetchers.CACHE_ONLY)\n          .enqueue(new GraphQLCall.Callback&lt;ListPetsQuery.Data&gt;() {\n              @Override\n              public void onResponse(@Nonnull Response&lt;ListPetsQuery.Data&gt; response) {\n                  List&lt;ListPetsQuery.Item&gt; items = new ArrayList&lt;&gt;();\n                  if (response.data() != null) {\n                      items.addAll(response.data().listPets().items());\n                  }\n\n                  items.add(new ListPetsQuery.Item(expected.__typename(),\n                          expected.id(),\n                          expected.name(),\n                          expected.description()));\n                  ListPetsQuery.Data data = new ListPetsQuery.Data(new ListPetsQuery.ListPets(&quot;ModelPetConnection&quot;, items, null));\n                  awsAppSyncClient.getStore().write(listEventsQuery, data).enqueue(null);\n                  Log.d(TAG, &quot;Successfully wrote item to local store while being offline.&quot;);\n\n                  finishIfOffline();\n              }\n\n              @Override\n              public void onFailure(@Nonnull ApolloException e) {\n                  Log.e(TAG, &quot;Failed to update event query list.&quot;, e);\n              }\n          });\n    }\n\n    private void finishIfOffline(){\n        \/\/ Close the add activity when offline otherwise allow callback to close\n        ConnectivityManager cm =\n                (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);\n\n        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();\n        boolean isConnected = activeNetwork != null &amp;&amp;\n                activeNetwork.isConnectedOrConnecting();\n\n        if (!isConnected) {\n            Log.d(TAG, &quot;App is offline. Returning to MainActivity .&quot;);\n            finish();\n        }\n    }<\/code><\/pre>\n<\/div>\n<p><code>query()<\/code> \uba54\uc11c\ub4dc\uac00 <code>CACHE_AND_NETWORK<\/code> \ubc29\uc2dd\uc744 \uc0ac\uc6a9\ud558\uae30 \ub54c\ubb38\uc5d0 <code>MainActivity<\/code>\ub97c \ubcc0\uacbd\ud560 \ud544\uc694\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub124\ud2b8\uc6cc\ud06c \ud638\ucd9c\uc744 \ud558\ub294 \ub3d9\uc548 \uba3c\uc800 \ub85c\uceec \uce90\uc2dc\uc5d0\uc11c \uc77d\uc2b5\ub2c8\ub2e4. \uc774\uc804\uc5d0 \ucd94\uac00\ud55c \uc560\uc644 \ub3d9\ubb3c\uc740 \ucd5c\uc801 \uc5c5\ub370\uc774\ud2b8 \ub54c\ubb38\uc5d0 \uc774\ubbf8 \ub85c\uceec \uce90\uc2dc\uc5d0 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\uc571\uc744 \uad6c\ucd95\ud558\uace0 \uc2e4\ud589\ud569\ub2c8\ub2e4. \ub85c\uadf8\uc778 \ud55c \ud6c4 \ube44\ud589\uae30 \ubaa8\ub4dc\ub97c \ucf2d\ub2c8\ub2e4.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"width: 300px\" src=\"https:\/\/d2908q01vomqb2.cloudfront.net\/0a57cb53ba59c46fc4b692527a38a87c78d84028\/2018\/12\/07\/airplanemode.png\" alt=\"\" width=\"339\" height=\"602\" \/><\/p>\n<p>\uc571\uc73c\ub85c \ub3cc\uc544\uac00\uc11c \uc0c8 \ud56d\ubaa9\uc744 \ucd94\uac00\ud558\uc2ed\uc2dc\uc624. UI \ud658\uacbd\uc740 \uc774\uc804\uc5d0 \uc628\ub77c\uc778 \uc0c1\ud0dc\uc600\ub358 \ub54c\uc640 \ub3d9\uc77c\ud574\uc57c \ud569\ub2c8\ub2e4. \uc774\ub984 \ubc0f \uc124\uba85\uc744 \uc785\ub825\ud569\ub2c8\ub2e4.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"width: 300px\" src=\"https:\/\/d2908q01vomqb2.cloudfront.net\/0a57cb53ba59c46fc4b692527a38a87c78d84028\/2018\/12\/07\/newpet.png\" alt=\"\" width=\"370\" height=\"658\" \/><\/p>\n<p><strong>Save<\/strong>\ub97c \uc120\ud0dd\ud569\ub2c8\ub2e4. \uc571\uc5d0 \ubaa9\ub85d\uc758 \ub450 \ubc88\uc9f8 \ud56d\ubaa9\uc774 \ud45c\uc2dc\ub418\uc5b4\uc57c \ud569\ub2c8\ub2e4.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"width: 300px\" src=\"https:\/\/d2908q01vomqb2.cloudfront.net\/0a57cb53ba59c46fc4b692527a38a87c78d84028\/2018\/12\/07\/twoitems.png\" alt=\"\" width=\"418\" height=\"743\" \/><\/p>\n<p>\uc774\uc81c \ube44\ud589\uae30 \ubaa8\ub4dc\ub97c \ud574\uc81c\ud558\uc2ed\uc2dc\uc624. \ud56d\ubaa9\uc740 \uc790\ub3d9\uc73c\ub85c \uc800\uc7a5\ub418\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc571\uc744 \ub2eb\uc558\ub2e4\uac00 \ub2e4\uc2dc \uc5f4\uc5b4\uc11c \ub3d9\uc77c\ud55c \ub450 \ud56d\ubaa9\uc774 \uacc4\uc18d \ud45c\uc2dc\ub418\ub294\uc9c0 \ud655\uc778\ud558\uba74 \uc800\uc7a5\uc774 \uc644\ub8cc\ub418\uc5c8\ub294\uc9c0 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<h2><strong>\uad6c\ub3c5 \uae30\ub2a5<br \/> <\/strong><\/h2>\n<p>AWS AppSync\ub97c \uc0ac\uc6a9\ud558\uba74 \uc2e4\uc2dc\uac04 \uc54c\ub9bc\uc744 \uc704\ud574 \uad6c\ub3c5\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\uc774 \uc139\uc158\uc5d0\uc11c\ub294 \uad6c\ub3c5\uc744 \uc0ac\uc6a9\ud558\uc5ec \ub2e4\ub978 \uc0ac\ub78c\uc774 \uc0c8 \uc560\uc644 \ub3d9\ubb3c\uc744 \ucd94\uac00\ud560 \ub54c \uc989\uc2dc \uc54c\ub824\uc90d\ub2c8\ub2e4. \uc774\ub97c \uc704\ud574 <code>MainActivity.java<\/code> \ud074\ub798\uc2a4\uc758 \ub05d\uc5d0 \ub2e4\uc74c \ube14\ub85d\uc744 \ucd94\uac00\ud574 \ubcf4\uaca0\uc2b5\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private AppSyncSubscriptionCall subscriptionWatcher;\n\n    private void subscribe(){\n        OnCreatePetSubscription subscription = OnCreatePetSubscription.builder().build();\n        subscriptionWatcher = ClientFactory.appSyncClient().subscribe(subscription);\n        subscriptionWatcher.execute(subCallback);\n    }\n\n    private AppSyncSubscriptionCall.Callback subCallback = new AppSyncSubscriptionCall.Callback() {\n        @Override\n        public void onResponse(@Nonnull Response response) {\n            Log.i(&quot;Response&quot;, &quot;Received subscription notification: &quot; + response.data().toString());\n\n            \/\/ Update UI with the newly added item\n            OnCreatePetSubscription.OnCreatePet data = ((OnCreatePetSubscription.Data)response.data()).onCreatePet();\n            final ListPetsQuery.Item addedItem = new ListPetsQuery.Item(data.__typename(), data.id(), data.name(), data.description());\n\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    mPets.add(addedItem);\n                    mAdapter.notifyItemInserted(mPets.size() - 1);\n                }\n            });\n        }\n\n        @Override\n        public void onFailure(@Nonnull ApolloException e) {\n            Log.e(&quot;Error&quot;, e.toString());\n        }\n\n        @Override\n        public void onCompleted() {\n            Log.i(&quot;Completed&quot;, &quot;Subscription completed&quot;);\n        }\n    };<\/code><\/pre>\n<\/div>\n<p>\uadf8\ub7f0 \ub2e4\uc74c, <code>onResume<\/code> \uba54\uc11c\ub4dc\ub97c \uc218\uc815\ud558\uc5ec \ub9c8\uc9c0\ub9c9\uc5d0 <code>subscribe<\/code>\ub97c \ud638\ucd9c\ud558\uc5ec \uc0c8 \uc560\uc644 \ub3d9\ubb3c \uc0dd\uc131\uc744 \uad6c\ub3c5\ud558\uc2ed\uc2dc\uc624. \ub610\ud55c \ud65c\ub3d9\uc774 \ub05d\ub098\uba74 \uad6c\ub3c5\uc744 \ucde8\uc18c\ud574\uc57c \ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">@Override\npublic void onResume() {\n      super.onResume();\n\n      query();\n      subscribe();\n}\n\n@Override\nprotected void onStop() {\n    super.onStop();\n    subscriptionWatcher.cancel();\n}<\/code><\/pre>\n<\/div>\n<p>\uc774\uc81c \ud14c\uc2a4\ud2b8\ud574 \ubd05\uc2dc\ub2e4. \uc5d0\ubbac\ub808\uc774\ud130\uc5d0\uc11c \uc571\uc744 \uad6c\ucd95\ud558\uace0 \uc2e4\ud589\ud558\uc2ed\uc2dc\uc624.<\/p>\n<p>\ub2e4\uc74c\uc73c\ub85c \ub450 \ubc88\uc9f8 \uc5d0\ubbac\ub808\uc774\ud130\ub97c \uc2dc\uc791\ud569\uc2dc\ub2e4. \ub450 \ubc88\uc9f8 \uc5d0\ubbac\ub808\uc774\ud130\ub97c \uc2dc\uc791\ud558\ub824\uba74 \uae30\ubcf8\uac12\uc744 \uc120\ud0dd \ucde8\uc18c\ud574\uc57c \ud569\ub2c8\ub2e4. (<strong>\uc2e4\ud589<\/strong>\uc744 \uc120\ud0dd\ud558\uace0 <strong>\uad6c\uc131 \ud3b8\uc9d1<\/strong>\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624. <strong>Android \uc571<\/strong>\uc5d0\uc11c <strong>\uc571<\/strong>\uc744 \uc120\ud0dd\ud558\uace0 <strong>\ud5a5\ud6c4 \ucd9c\uc2dc\uc2dc \uac19\uc740 \ub514\ubc14\uc774\uc2a4 \uc0ac\uc6a9 \ud655\uc778\ub780<\/strong>\uc758 \uc120\ud0dd\uc744 \ud574\uc81c\ud558\uc2ed\uc2dc\uc624.<\/p>\n<p>\ub610\ud55c AVD \uad00\ub9ac\uc790\uc5d0 \ub2e4\ub978 \uc5d0\ubbac\ub808\uc774\ud130 \ub514\ubc14\uc774\uc2a4 \uc720\ud615\uc774 \uc788\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624. \uc571\uc744 \uc2e4\ud589\ud558\uace0 \ub450 \ubc88\uc9f8 \uc5d0\ubbac\ub808\uc774\ud130 \ub514\ubc14\uc774\uc2a4\ub97c \uc120\ud0dd\ud55c \ub2e4\uc74c \ub450 \uac1c\uc758 \uc5d0\ubbac\ub808\uc774\ud130\uc5d0\uc11c \uc571\uc744 \ub098\ub780\ud788 \uc2e4\ud589\ud558\uc2ed\uc2dc\uc624. \ub450 \ub514\ubc14\uc774\uc2a4\uc5d0\uc11c \uc560\uc644 \ub3d9\ubb3c\uc758 \ubaa9\ub85d\uc744 \ud655\uc778\ud558\ub3c4\ub85d \ub450 \uae30\uae30\uc5d0 \ubaa8\ub450 \ub85c\uadf8\uc778\ud574\uc57c \ud569\ub2c8\ub2e4.<\/p>\n<p>\uc571 \uc911 \ud558\ub098\uc5d0 \ub2e4\ub978 \uc560\uc644 \ub3d9\ubb3c\uc744 \ucd94\uac00\ud558\uace0 \ub2e4\ub978 \uc571\uc5d0 \ud45c\uc2dc\ub418\ub294\uc9c0 \ud655\uc778\ud569\ub2c8\ub2e4. \uc9e0!<\/p>\n<h2><strong>\uc2a4\ud1a0\ub9ac\uc9c0\ub97c \uc774\uc6a9\ud55c \uc791\uc5c5<\/strong><\/h2>\n<p>AWS Amplify\ub97c \uc0ac\uc6a9\ud558\uba74 Amazon S3\ub97c \uc0ac\uc6a9\ud558\uc5ec \uac1d\uccb4 \uc2a4\ud1a0\ub9ac\uc9c0 \uc9c0\uc6d0\uc744 \uc27d\uac8c \ucd94\uac00\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. AWS Amplify\ub294 \uc790\ub3d9\uc73c\ub85c \ubc84\ud0b7 \ud504\ub85c\ube44\uc800\ub2dd \ubc0f \uad8c\ud55c \uad6c\uc131\uc744 \uad00\ub9ac\ud569\ub2c8\ub2e4.<\/p>\n<h3><strong>AWS Amplify \uc5c5\ub370\uc774\ud2b8<\/strong><\/h3>\n<p>\uc2dc\uc791\ud558\ub824\uba74 <code>.\/amplify\/backend\/api\/&lt;PROJECTNAME&gt;\/schema.graphql<\/code>\uc5d0\uc11c \ub85c\uceec \uc2a4\ud0a4\ub9c8\ub97c \uc218\uc815\ud558\uc5ec \uc0ac\uc9c4 \ubb38\uc790\uc5f4 \uc18d\uc131\uc744 \ucd94\uac00\ud558\uc2ed\uc2dc\uc624.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-json\">type Pet @model {\n  id: ID!\n  name: String!\n  description: String\n  photo: String\n}\n<\/code><\/pre>\n<\/div>\n<p>\uadf8\ub7f0 \ub2e4\uc74c, \ub8e8\ud2b8 \ub514\ub809\ud130\ub9ac\ub85c \uc774\ub3d9\ud558\uc5ec \uba85\ub839\uc904\uc5d0\uc11c \ub2e4\uc74c\uc744 \uc2e4\ud589\ud558\uc2ed\uc2dc\uc624.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-bash\">amplify add storage<\/code><\/pre>\n<\/div>\n<p>\ub2e4\uc74c \uc9c8\ubb38\uc5d0 \ub2f5\ud558\uc2ed\uc2dc\uc624.<\/p>\n<ul>\n<li>\uc544\ub798\uc5d0 \uc5b8\uae09\ub41c \uc11c\ube44\uc2a4 \uc911 \ud558\ub098\ub97c \uc120\ud0dd: <strong>\ucf58\ud150\uce20(\uc774\ubbf8\uc9c0, \uc624\ub514\uc624, \ube44\ub514\uc624 \ub4f1)<\/strong><\/li>\n<li>\ud504\ub85c\uc81d\ud2b8\uc5d0\uc11c \uc774 \uce74\ud14c\uace0\ub9ac\uc5d0 \ub808\uc774\ube14\uc744 \uc9c0\uc815\ud558\ub294 \ub370 \uc0ac\uc6a9\ud560 \ub9ac\uc18c\uc2a4\uc5d0 \uce5c\uadfc\ud55c \uc774\ub984 \uc9c0\uc815: <strong>MyPetAppResources<\/strong><\/li>\n<li>\ubc84\ud0b7 \uc774\ub984 \uc785\ub825: <strong>mypetapp1246e0cde8074f78b94363dbe73f8adfdsfds<\/strong>(<em>\ub610\ub294 \uace0\uc720 \ud55c \ud56d\ubaa9<\/em>)<\/li>\n<li>\uc561\uc138\uc2a4 \uad8c\ud55c: <strong>\uc778\uc99d \uc0ac\uc6a9\uc790\ub9cc<\/strong><\/li>\n<li>\uc778\uc99d\ub41c \uc0ac\uc6a9\uc790\uc5d0\uac8c \ud544\uc694\ud55c \uc561\uc138\uc2a4 \uc720\ud615: <strong>\uc77d\uae30\/\uc4f0\uae30<\/strong><\/li>\n<\/ul>\n<p>\uadf8\ub7f0 \ub2e4\uc74c, \uc544\ub798\uc640 \uac19\uc774 \uc2e4\ud589\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-bash\">amplify push<\/code><\/pre>\n<\/div>\n<p>\ucf54\ub4dc\ub97c \uc5c5\ub370\uc774\ud2b8\ud558\uace0 GraphQL \ubb38\uc744 \uc7ac\uc0dd\uc131\ud560\uc9c0 \uc5ec\ubd80\ub97c \ubb3b\ub294 \uba54\uc2dc\uc9c0\uac00 \ub098\ud0c0\ub098\uba74 <strong>\ub124<\/strong>\ub97c \uc120\ud0dd\ud569\ub2c8\ub2e4. <strong>Enter<\/strong>\ub97c \uc120\ud0dd\ud558\uace0 AWS CloudFormation \uc5c5\ub370\uc774\ud2b8\uac00 \uc644\ub8cc \ub420 \ub54c\uae4c\uc9c0 \uae30\ub2e4\ub9bd\ub2c8\ub2e4. \uc774 \uc791\uc5c5\uc5d0\ub294 \uba87 \ubd84\uc774 \uac78\ub9bd\ub2c8\ub2e4.<\/p>\n<h3><strong>\uc2a4\ud1a0\ub9ac\uc9c0 \uc885\uc18d\uc131 \ucd94\uac00<\/strong><\/h3>\n<p>\uadf8 \ub3d9\uc548, \ud504\ub7f0\ud2b8\uc5d4\ub4dc \ud074\ub77c\uc774\uc5b8\ud2b8 \ucf54\ub4dc\ub97c \uc5c5\ub370\uc774\ud2b8\ud574 \ubd05\uc2dc\ub2e4.<\/p>\n<p><code>AndroidManifest.xml<\/code>\uc744 \uc5f4\uace0 <code>&lt;application&gt;<\/code>\uc5d0 <code>TransferService<\/code>\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-xml\">&lt;application&gt;\n    &lt;!-- ...other code... --&gt;\n    &lt;service android:name=&quot;com.amazonaws.mobileconnectors.s3.transferutility.TransferService&quot; \/&gt;\n&lt;\/application&gt;<\/code><\/pre>\n<\/div>\n<p>\uc571\uc758 <code>build.gradle<\/code>\uc744 \uc5f4\uace0 Amazon S3\uc5d0 \ub300\ud55c \uc885\uc18d\uc131\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-xml\">implementation 'com.amazonaws:aws-android-sdk-s3:2.7.+'<\/code><\/pre>\n<\/div>\n<h3><strong>\uc0ac\uc9c4 \uc120\ud0dd \ucf54\ub4dc \ucd94\uac00<\/strong><\/h3>\n<p>\uadf8\ub7f0 \ub2e4\uc74c, <code>AddPetActivity.java<\/code>\ub97c \uc5f4\uace0 \uc0ac\uc9c4 \uc120\ud0dd \ucf54\ub4dc\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\"> \/\/ Photo selector application code.\n  private static int RESULT_LOAD_IMAGE = 1;\n  private String photoPath;\n\n  public void choosePhoto() {\n      Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);\n      startActivityForResult(i, RESULT_LOAD_IMAGE);\n  }\n\n  @Override\n  protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n      super.onActivityResult(requestCode, resultCode, data);\n      if (requestCode == RESULT_LOAD_IMAGE &amp;&amp; resultCode == RESULT_OK &amp;&amp; null != data) {\n          Uri selectedImage = data.getData();\n          String[] filePathColumn = {MediaStore.Images.Media.DATA};\n          Cursor cursor = getContentResolver().query(selectedImage,\n                  filePathColumn, null, null, null);\n          cursor.moveToFirst();\n          int columnIndex = cursor.getColumnIndex(filePathColumn[0]);\n          String picturePath = cursor.getString(columnIndex);\n          cursor.close();\n          \/\/ String picturePath contains the path of selected Image\n          photoPath = picturePath;\n      }\n  }<\/code><\/pre>\n<\/div>\n<p>UI\uc5d0\uc11c \uc5c5\ub85c\ub4dc \uc0ac\uc9c4\uc744 \ud638\ucd9c\ud574\uc57c \ud569\ub2c8\ub2e4. <code>activity_add_pet.xml<\/code>\uc744 \uc5f4\uace0 <strong>Save<\/strong> \ubc84\ud2bc \uc55e\uc5d0 \ubc84\ud2bc\uc744 \ucd94\uac00\ud558\uc2ed\uc2dc\uc624.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-xml\">&lt;LinearLayout&gt;\n &lt;!-- ... other code... --&gt;\n  &lt;Button\n      android:layout_width=&quot;wrap_content&quot;\n      android:layout_height=&quot;wrap_content&quot;\n      android:id=&quot;@+id\/btn_add_photo&quot;\n      android:layout_marginTop=&quot;15dp&quot;\n      android:text=&quot;Add Photo&quot;\/&gt;\n\n  &lt;Button\n      android:layout_width=&quot;wrap_content&quot;\n      android:layout_height=&quot;wrap_content&quot;\n      android:id=&quot;@+id\/btn_save&quot;\n      android:layout_marginTop=&quot;15dp&quot;\n      android:text=&quot;Save&quot;\/&gt;\n&lt;\/LinearLayout&gt;<\/code><\/pre>\n<\/div>\n<p>\uc774\uc81c <code>choosePhoto()<\/code> \uba54\uc11c\ub4dc\uc5d0 \uc774 \ubc84\ud2bc\uc744 \uc5f0\uacb0\ud569\uc2dc\ub2e4. <code>AddPetActivity.java<\/code>\ub85c \ub3cc\uc544\uac00\uc11c <code>onCreate<\/code>\ub97c \uc218\uc815\ud558\uc5ec \ud074\ub9ad \ub9ac\uc2a4\ub108\ub97c <strong>Save<\/strong> \ubc84\ud2bc\uc5d0 \ucca8\ubd80\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">@Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_add_pet);\n\n        Button btnAddItem = findViewById(R.id.btn_save);\n        btnAddItem.setOnClickListener(new View.OnClickListener() {\n\n            @Override\n            public void onClick(View view) {\n                    save();\n            }\n        });\n\n        Button btnAddPhoto = findViewById(R.id.btn_add_photo);\n        btnAddPhoto.setOnClickListener(new View.OnClickListener() {\n\n            @Override\n            public void onClick(View view) {\n                choosePhoto();\n            }\n        });\n    }<\/code><\/pre>\n<\/div>\n<p>\uc571\uc744 \uad6c\ucd95\ud558\uace0 \uc2e4\ud589\ud558\uc5ec \uc0ac\uc9c4 \uc120\ud0dd \ubc84\ud2bc\uc774 \uc791\ub3d9\ud558\ub294\uc9c0 \ud655\uc778\ud569\ub2c8\ub2e4.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"width: 300px\" src=\"https:\/\/d2908q01vomqb2.cloudfront.net\/0a57cb53ba59c46fc4b692527a38a87c78d84028\/2018\/12\/07\/addphotobutton.png\" alt=\"\" width=\"424\" height=\"754\" \/><\/p>\n<p><strong>ADD PHOTO<\/strong>\ub97c \uc120\ud0dd\ud569\ub2c8\ub2e4. \uac24\ub7ec\ub9ac\uc5d0\uc11c \uc0ac\uc9c4\uc744 \uc120\ud0dd\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4. (\uc5d0\ubbac\ub808\uc774\ud130\uc5d0 \uc0ac\uc9c4\uc774 \uc5c6\uc73c\uba74 \ube0c\ub77c\uc6b0\uc800\ub97c \uc5f4\uace0 \uc778\ud130\ub137\uc5d0\uc11c \uc77c\ubd80 \uc0ac\uc9c4\uc744 \ub2e4\uc6b4\ub85c\ub4dc\ud558\uc2ed\uc2dc\uc624.)<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"width: 300px\" src=\"https:\/\/d2908q01vomqb2.cloudfront.net\/0a57cb53ba59c46fc4b692527a38a87c78d84028\/2018\/12\/07\/selectphoto.png\" alt=\"\" width=\"389\" height=\"695\" \/><\/p>\n<p>&nbsp;<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"width: 300px\" src=\"https:\/\/d2908q01vomqb2.cloudfront.net\/0a57cb53ba59c46fc4b692527a38a87c78d84028\/2018\/12\/07\/selectphoto2.png\" alt=\"\" width=\"395\" height=\"702\" \/><\/p>\n<p>\uc0ac\uc9c4\uc744 \uc120\ud0dd\ud55c \ud6c4\uc5d0\ub294 <strong>ADD<\/strong> \ud654\uba74\uc73c\ub85c \ub2e4\uc2dc \uc774\ub3d9\ud574\uc57c \ud569\ub2c8\ub2e4.<\/p>\n<h3><strong>Amazon S3 \uc0ac\uc9c4 \uc5c5\ub85c\ub4dc \ucf54\ub4dc \ucd94\uac00<\/strong><\/h3>\n<p>\uc774\uc81c \uc0ac\uc9c4\uc744 \uc120\ud0dd\ud560 \uc218 \uc788\uc73c\ubbc0\ub85c \uc0ac\uc9c4\uc744 \uc5c5\ub85c\ub4dc\ud558\uc5ec \ubc31\uc5d4\ub4dc\uc5d0 \uc800\uc7a5\ud574\uc57c \ud569\ub2c8\ub2e4. <code>TransferUtility<\/code>\ub97c \uc0ac\uc6a9\ud558\uc5ec Amazon S3 \ud30c\uc77c \uc5c5\ub85c\ub4dc \ubc0f \ub2e4\uc6b4\ub85c\ub4dc \uc791\uc5c5\uc744 \ucc98\ub9ac\ud560 \uac83\uc785\ub2c8\ub2e4. \ucd08\uae30\ud654 \ucf54\ub4dc\ub97c<code>ClientFactory.java class<\/code>\uc5d0 \ucd94\uac00\ud569\uc2dc\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private static volatile TransferUtility transferUtility;\n\npublic static synchronized void init(final Context context) {\n    \/\/ ... appsyncClient initialization code ...\n\n    if (transferUtility == null) {\n        transferUtility = TransferUtility.builder()\n                .context(context)\n                .awsConfiguration(AWSMobileClient.getInstance().getConfiguration())\n                .s3Client(new AmazonS3Client(AWSMobileClient.getInstance()))\n                .build();\n        \n    }\n}\npublic static synchronized TransferUtility transferUtility() {\n        return transferUtility;\n}<\/code><\/pre>\n<\/div>\n<p>\uadf8\ub7f0 \ub2e4\uc74c, <code>AddPetActivity.java<\/code>\uc5d0\uc11c <code>TransferUtility<\/code>\ub97c \uc774\uc6a9\ud574 \uc0ac\uc9c4\uc744 \uc5c5\ub85c\ub4dc\ud558\ub294 \ucf54\ub4dc\ub97c \ucd94\uac00\ud574 \ubcf4\uaca0\uc2b5\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private String getS3Key(String localPath) {\n    \/\/We have read and write ability under the public folder\n    return &quot;public\/&quot; + new File(localPath).getName();\n}\n\npublic void uploadWithTransferUtility(String localPath) {\n    String key = getS3Key(localPath);\n\n    Log.d(TAG, &quot;Uploading file from &quot; + localPath + &quot; to &quot; + key);\n\n    TransferObserver uploadObserver =\n            ClientFactory.transferUtility().upload(\n                    key,\n                    new File(localPath));\n\n    \/\/ Attach a listener to the observer to get state update and progress notifications\n    uploadObserver.setTransferListener(new TransferListener() {\n\n        @Override\n        public void onStateChanged(int id, TransferState state) {\n            if (TransferState.COMPLETED == state) {\n                \/\/ Handle a completed upload.\n                Log.d(TAG, &quot;Upload is completed. &quot;);\n\n                \/\/ Upload is successful. Save the rest and send the mutation to server.\n                save();\n            }\n        }\n\n        @Override\n        public void onProgressChanged(int id, long bytesCurrent, long bytesTotal) {\n            float percentDonef = ((float) bytesCurrent \/ (float) bytesTotal) * 100;\n            int percentDone = (int)percentDonef;\n\n            Log.d(TAG, &quot;ID:&quot; + id + &quot; bytesCurrent: &quot; + bytesCurrent\n                    + &quot; bytesTotal: &quot; + bytesTotal + &quot; &quot; + percentDone + &quot;%&quot;);\n        }\n\n        @Override\n        public void onError(int id, Exception ex) {\n            \/\/ Handle errors\n            Log.e(TAG, &quot;Failed to upload photo. &quot;, ex);\n\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    Toast.makeText(AddPetActivity.this, &quot;Failed to upload photo&quot;, Toast.LENGTH_LONG).show();\n                }\n            });\n        }\n\n    });\n}<\/code><\/pre>\n<\/div>\n<h3><strong>\uc0ac\uc9c4 \uc800\uc7a5<br \/> <\/strong><\/h3>\n<p>\uc560\uc644 \ub3d9\ubb3c \uac1d\uccb4\uc5d0 \uc0c8 \uc18d\uc131\uc744 \ucd94\uac00\ud588\uc73c\ubbc0\ub85c \uc774\ub97c \uc218\uc6a9\ud558\ub3c4\ub85d \uc788\ub3c4\ub85d \ucf54\ub4dc\ub97c \uc218\uc815\ud574\uc57c \ud569\ub2c8\ub2e4. <code>AddPetActivity.java<\/code>\uc5d0\uc11c \ub2e4\uc74c\uacfc \uac19\uc740 \uba54\uc11c\ub4dc\ub97c \ucd94\ucd9c\ud558\uc5ec \uc0ac\uc9c4\uc774 \uc120\ud0dd\ub418\uc5c8\ub294\uc9c0 \uc5ec\ubd80\uc5d0 \ub530\ub77c \ub2e4\ub978 \uc720\ud615\uc758 <code>CreatePetInput<\/code>\uc744 \uc0dd\uc131\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private CreatePetInput getCreatePetInput() {\n    final String name = ((EditText) findViewById(R.id.editTxt_name)).getText().toString();\n    final String description = ((EditText) findViewById(R.id.editText_description)).getText().toString();\n\n    if (photoPath != null &amp;&amp; !photoPath.isEmpty()){\n        return CreatePetInput.builder()\n                .name(name)\n                .description(description)\n                .photo(getS3Key(photoPath)).build();\n    } else {\n        return CreatePetInput.builder()\n                .name(name)\n                .description(description)\n                .build();\n    }\n}<\/code><\/pre>\n<\/div>\n<p>\uadf8\ub7f0 \ub2e4\uc74c, \ucd94\ucd9c\ub41c \uba54\uc18c\ub4dc\ub97c \ud638\ucd9c\ud558\ub3c4\ub85d <code>save()<\/code>\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private void save() {\n    CreatePetInput input = getCreatePetInput();\n\n    CreatePetMutation addPetMutation = CreatePetMutation.builder()\n            .input(input)\n            .build();\n\n    ClientFactory.appSyncClient().mutate(addPetMutation).\n            refetchQueries(ListPetsQuery.builder().build()).\n            enqueue(mutateCallback);\n\n    \/\/ Enables offline support via an optimistic update\n    \/\/ Add to event list while offline or before request returns\n    addPetOffline(input);\n}<\/code><\/pre>\n<\/div>\n<p><code>CreatePet<\/code> \ubcc0\ud615\uc744 \ubcc0\uacbd\ud588\uc73c\ubbc0\ub85c <code>addPetOffline<\/code> \ucf54\ub4dc\ub3c4 \uc218\uc815\ud574\uc57c \ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private void addPetOffline(final CreatePetInput input) {\n\n    final CreatePetMutation.CreatePet expected =\n            new CreatePetMutation.CreatePet(\n                    &quot;Pet&quot;,\n                    UUID.randomUUID().toString(),\n                    input.name(),\n                    input.description(),\n                    input.photo());\n\n    final AWSAppSyncClient awsAppSyncClient = ClientFactory.appSyncClient();\n    final ListPetsQuery listPetsQuery = ListPetsQuery.builder().build();\n\n    awsAppSyncClient.query(listPetsQuery)\n            .responseFetcher(AppSyncResponseFetchers.CACHE_ONLY)\n            .enqueue(new GraphQLCall.Callback&lt;ListPetsQuery.Data&gt;() {\n                @Override\n                public void onResponse(@Nonnull Response&lt;ListPetsQuery.Data&gt; response) {\n                    List&lt;ListPetsQuery.Item&gt; items = new ArrayList&lt;&gt;();\n                    if (response.data() != null) {\n                        items.addAll(response.data().listPets().items());\n                    }\n\n                    items.add(new ListPetsQuery.Item(expected.__typename(),\n                            expected.id(),\n                            expected.name(),\n                            expected.description(),\n                            expected.photo()));\n                    ListPetsQuery.Data data = new ListPetsQuery.Data(\n                            new ListPetsQuery.ListPets(&quot;ModelPetConnection&quot;, items, null));\n                    awsAppSyncClient.getStore().write(listPetsQuery, data).enqueue(null);\n                    Log.d(TAG, &quot;Successfully wrote item to local store while being offline.&quot;);\n\n                    finishIfOffline();\n                }\n\n                @Override\n                public void onFailure(@Nonnull ApolloException e) {\n                    Log.e(TAG, &quot;Failed to update event query list.&quot;, e);\n                }\n            });\n    }<\/code><\/pre>\n<\/div>\n<p>\uadf8\ub7f0 \ub2e4\uc74c, <code>uploadAndSave()<\/code>\ub77c\ub294 \uc0c8\ub85c\uc6b4 \uba54\uc11c\ub4dc\ub97c \ub9cc\ub4e4\uc5b4 \uc0ac\uc9c4 \ubc0f \uc0ac\uc9c4 \uc800\uc7a5 \uc791\uc5c5\uc744 \ubaa8\ub450 \ucc98\ub9ac\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private void uploadAndSave(){\n\n    if (photoPath != null) {\n      \/\/ For higher Android levels, we need to check permission at runtime\n      if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)\n              != PackageManager.PERMISSION_GRANTED) {\n          \/\/ Permission is not granted\n          Log.d(TAG, &quot;READ_EXTERNAL_STORAGE permission not granted! Requesting...&quot;);\n          ActivityCompat.requestPermissions(this,\n                  new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},\n                  1);\n      }\n\n      \/\/ Upload a photo first. We will only call save on its successful callback.\n      uploadWithTransferUtility(photoPath);\n    } else {\n        save();\n    }\n}<\/code><\/pre>\n<\/div>\n<p>\uc774\uc81c <strong>\uc800\uc7a5<\/strong> \ubc84\ud2bc\uc744 \uc120\ud0dd\ud558\uba74 <code>uploadAndSave()<\/code> \ud568\uc218\ub97c \ud638\ucd9c\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">protected void onCreate(Bundle savedInstanceState) {\n    \/\/ ... other code ...\n    btnAddItem.setOnClickListener(new View.OnClickListener() {\n\n        @Override\n        public void onClick(View view) {\n            uploadAndSave();\n        }\n    });<\/code><\/pre>\n<\/div>\n<p>\ub610\ud55c <code>photo<\/code> \uc18d\uc131\uc774 \uc0c8\ub85c \ucd94\uac00\ub418\uc5c8\uae30 \ub54c\ubb38\uc5d0 <code>MainActivity.java<\/code>\uc5d0\uc11c \uad6c\ub3c5 \ucf5c\ubc31\uc744 \uc5c5\ub370\uc774\ud2b8\ud574\uc57c \ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">private AppSyncSubscriptionCall.Callback subCallback = new AppSyncSubscriptionCall.Callback() {\n        @Override\n        public void onResponse(@Nonnull Response response) {\n            Log.i(&quot;Response&quot;, &quot;Received subscription notification: &quot; + response.data().toString());\n\n            \/\/ Update UI with the newly added item\n            OnCreatePetSubscription.OnCreatePet data = ((OnCreatePetSubscription.Data)response.data()).onCreatePet();\n\n            final ListPetsQuery.Item addedItem =\n                    new ListPetsQuery.Item(data.__typename(), data.id(), data.name(), data.description(), data.photo());\n\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    mPets.add(addedItem);\n                    mAdapter.notifyItemInserted(mPets.size() - 1);\n                }\n            });\n        }\n        \/\/...other event handlers ...\n    };<\/code><\/pre>\n<\/div>\n<h3><strong>\uc0ac\uc9c4 \ub2e4\uc6b4\ub85c\ub4dc \ubc0f \ud45c\uc2dc<\/strong><\/h3>\n<p>\uc774\uc81c \uc0ac\uc9c4\uc744 \uc800\uc7a5\ud558\ub294 \uae30\ub2a5\uc744 \uad6c\ud604 \ud588\uc73c\ubbc0\ub85c \uc0ac\uc9c4\uc774 \ub2e4\uc6b4\ub85c\ub4dc\ub418\uace0 \ud45c\uc2dc\ub418\ub294\uc9c0 \ud655\uc778\ud569\uc2dc\ub2e4.<\/p>\n<p><code>recyclerview_row.xml<\/code>\uc744 \uc5f4\uace0, <code>&lt;ImageView&gt;<\/code>\ub97c \ucd94\uac00\ud55c \ud6c4 \ub808\uc774\uc544\uc6c3\uc744 \ub2e4\uc74c\uacfc \uac19\uc774 \uc218\uc815\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-xml\">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\n&lt;LinearLayout\n    xmlns:android=&quot;http:\/\/schemas.android.com\/apk\/res\/android&quot;\n    android:layout_width=&quot;match_parent&quot;\n    android:layout_height=&quot;wrap_content&quot;\n    android:orientation=&quot;horizontal&quot;\n    android:padding=&quot;10dp&quot;\n    android:weightSum=&quot;100&quot;&gt;\n\n    &lt;ImageView\n        android:id=&quot;@+id\/image_view&quot;\n        android:layout_width=&quot;0dp&quot;\n        android:layout_height=&quot;match_parent&quot;\n        android:maxHeight=&quot;200dp&quot;\n        android:layout_weight=&quot;30&quot;\n        \/&gt;\n\n    &lt;LinearLayout\n        android:layout_width=&quot;0dp&quot;\n        android:layout_height=&quot;match_parent&quot;\n        android:orientation=&quot;vertical&quot;\n        android:layout_weight=&quot;70&quot;\n        android:layout_marginTop=&quot;10dp&quot;&gt;\n\n        &lt;TextView\n            android:id=&quot;@+id\/txt_name&quot;\n            android:layout_width=&quot;match_parent&quot;\n            android:layout_height=&quot;wrap_content&quot;\n            android:textSize=&quot;15dp&quot;\n            android:paddingLeft=&quot;10dp&quot; \/&gt;\n\n        &lt;TextView\n            android:id=&quot;@+id\/txt_description&quot;\n            android:layout_width=&quot;match_parent&quot;\n            android:layout_height=&quot;wrap_content&quot;\n            android:textSize=&quot;15dp&quot;\n            android:paddingLeft=&quot;10dp&quot; \/&gt;\n    &lt;\/LinearLayout&gt;\n\n&lt;\/LinearLayout&gt;<\/code><\/pre>\n<\/div>\n<p><code>MyAdapter.java<\/code>\ub97c \uc5ec\uc2ed\uc2dc\uc624. \uc0ac\uc9c4 \uc18d\uc131\uc5d0 \ud574\ub2f9\ud558\ub294 \ucf54\ub4dc\ub97c \ucd94\uac00\ud558\uace0 \uc0ac\uc9c4\uc774 \uc788\ub294 \uacbd\uc6b0 \uc0ac\uc9c4\uc744 \ub2e4\uc6b4\ub85c\ub4dc\ud558\uc2ed\uc2dc\uc624.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">public class MyAdapter extends RecyclerView.Adapter&lt;MyAdapter.ViewHolder&gt; {\n    \/\/ ... other code ...\n\n    \/\/ stores and recycles views as they are scrolled off screen\n    class ViewHolder extends RecyclerView.ViewHolder {\n        TextView txt_name;\n        TextView txt_description;\n        ImageView image_view;\n        String localUrl;\n\n        ViewHolder(View itemView) {\n            super(itemView);\n            txt_name = itemView.findViewById(R.id.txt_name);\n            txt_description = itemView.findViewById(R.id.txt_description);\n            image_view = itemView.findViewById(R.id.image_view);\n        }\n\n        void bindData(ListPetsQuery.Item item) {\n            txt_name.setText(item.name());\n            txt_description.setText(item.description());\n\n            if (item.photo() != null) {\n                if (localUrl == null) {\n                    downloadWithTransferUtility(item.photo());\n                } else {\n                    image_view.setImageBitmap(BitmapFactory.decodeFile(localUrl));\n                }\n            }\n            else\n                image_view.setImageBitmap(null);\n        }\n\n        private void downloadWithTransferUtility(final String photo) {\n            final String localPath = Environment.getExternalStoragePublicDirectory(\n                    Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + &quot;\/&quot; + photo;\n\n            TransferObserver downloadObserver =\n                    ClientFactory.transferUtility().download(\n                            photo,\n                            new File(localPath));\n\n            \/\/ Attach a listener to the observer to get state update and progress notifications\n            downloadObserver.setTransferListener(new TransferListener() {\n\n                @Override\n                public void onStateChanged(int id, TransferState state) {\n                    if (TransferState.COMPLETED == state) {\n                        \/\/ Handle a completed upload.\n                        localUrl = localPath;\n                        image_view.setImageBitmap(BitmapFactory.decodeFile(localPath));\n                    }\n                }\n\n                @Override\n                public void onProgressChanged(int id, long bytesCurrent, long bytesTotal) {\n                    float percentDonef = ((float) bytesCurrent \/ (float) bytesTotal) * 100;\n                    int percentDone = (int) percentDonef;\n\n                    Log.d(TAG, &quot;   ID:&quot; + id + &quot;   bytesCurrent: &quot; + bytesCurrent + &quot;   bytesTotal: &quot; + bytesTotal + &quot; &quot; + percentDone + &quot;%&quot;);\n                }\n\n                @Override\n                public void onError(int id, Exception ex) {\n                    \/\/ Handle errors\n                    Log.e(TAG, &quot;Unable to download the file.&quot;, ex);\n                }\n            });\n        }\n    }\n}<\/code><\/pre>\n<\/div>\n<p>\uc0ac\uc9c4\uc744 \ub2e4\uc6b4\ub85c\ub4dc\ud558\uae30 \ub54c\ubb38\uc5d0 \uad8c\ud55c\uc744 \ubd80\uc5ec \ubc1b\uc744 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4. <code>MainActivity.java<\/code>\ub85c \uc774\ub3d9\ud558\uc5ec <code>query()<\/code>\uc5d0\uc11c \uad8c\ud55c \ucc3e\uae30 \ube14\ub85d\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.<\/p>\n<div class=\"hide-language\">\n<pre><code class=\"lang-java\">public void query(){\n    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)\n            != PackageManager.PERMISSION_GRANTED) {\n        \/\/ Permission is not granted\n        Log.d(TAG, &quot;WRITE_EXTERNAL_STORAGE permission not granted! Requesting...&quot;);\n        ActivityCompat.requestPermissions(this,\n                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},\n                2);\n    }\n\n    ClientFactory.appSyncClient().query(ListPetsQuery.builder().build())\n            .responseFetcher(AppSyncResponseFetchers.CACHE_AND_NETWORK)\n            .enqueue(queryCallback);\n}<\/code><\/pre>\n<\/div>\n<p>\ub4dc\ub514\uc5b4 \ubaa8\ub4e0 \uc791\uc5c5\uc744 \ub9c8\ucce4\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc571\uc744 \uad6c\ucd95\ud558\uace0 \uc2e4\ud589\ud569\ub2c8\ub2e4. \uc0ac\uc9c4\uc744 \ucd94\uac00\ud558\uace0 \uba4b\uc9c4 \uc560\uc644 \ub3d9\ubb3c\uc744 \ubcfc \uc218 \uc788\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624! \uc571\uc740 \ub2e4\uc74c\uacfc \uac19\uc740 \ud615\ud0dc\ub85c \ub098\ud0c0\ub0a0 \uac83\uc785\ub2c8\ub2e4.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"width: 300px\" src=\"https:\/\/d2908q01vomqb2.cloudfront.net\/0a57cb53ba59c46fc4b692527a38a87c78d84028\/2018\/12\/07\/final.png\" alt=\"\" width=\"412\" height=\"731\" \/><\/p>\n<h2><strong>\uae30\ud0c0 \uae30\ub2a5<\/strong><\/h2>\n<p>\uc571\uc758 \uae30\ub2a5\uc744 \uac15\ud654\ud560 \uc218 \uc788\ub294 \ub2e4\ub978 \uae30\ub2a5\ub3c4 \uc788\uc2b5\ub2c8\ub2e4. \ub2e4\uc74c \ub0b4\uc6a9\uc744 \uc9c1\uc811 \uc5f0\uc2b5\ud574 \ubcf4\uc2ed\uc2dc\uc624.<\/p>\n<ul>\n<li>\uc560\uc644 \ub3d9\ubb3c\uc758 \uc815\ubcf4\ub97c \uc5c5\ub370\uc774\ud2b8\ud558\ub294 \uae30\ub2a5\uc744 \ucd94\uac00\ud558\uc2ed\uc2dc\uc624.<\/li>\n<li>\uc560\uc644 \ub3d9\ubb3c\uc744 \uc0ad\uc81c\ud558\ub294 \uae30\ub2a5\uc744 \ucd94\uac00\ud558\uc2ed\uc2dc\uc624.<\/li>\n<li>\ubcc0\ud615\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 \uc0ad\uc81c\ud558\ub824\uba74 \uad6c\ub3c5\ud558\uc2ed\uc2dc\uc624.<\/li>\n<\/ul>\n<p>Android \uc571\uc5d0 \uc624\ud504\ub77c\uc778 \uc9c0\uc6d0, \uc2e4\uc2dc\uac04 \uad6c\ub3c5 \ubc0f \uac1d\uccb4 \uc800\uc7a5\uc18c\ub97c \ucd94\uac00 \ud560 \uc218 \uc788\uac8c \ub418\uc5c8\uc2b5\ub2c8\ub2e4.&nbsp;&nbsp; \ub354 \uc790\uc138\ud55c \uac83\uc740 <a href=\"https:\/\/aws-amplify.github.io\/\">AWS Amplify \uc6f9 \uc0ac\uc774\ud2b8<\/a>\uc5d0\uc11c AWS Amplify\uc5d0\uc11c \uc0b4\ud3b4 \ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\u2013 Jane Shen, AWS \ud504\ub85c\ud398\uc154\ub110 \uc11c\ube44\uc2a4 \uc560\ud50c\ub9ac\ucf00\uc774\uc158 \uc544\ud0a4\ud14d\ud2b8<\/p>\n<p><em>\ud639\uc2dc \ub9cc\uc57d React\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc0ac\uc6a9\uc790\ub4e4\uc774 \uc0ac\uc9c4\uc744 \uc5c5\ub85c\ub4dc\ud558\uace0 \uacf5\uc720\ud558\ub294 data-driven \uae30\ubc18\uc758 \uc548\uc804\ud55c \uc0ac\uc9c4 \uac24\ub7ec\ub9ac \uc6f9 \uc560\ud50c\ub9ac\ucf00\uc774\uc158 \uac1c\ubc1c\uc744 \ud558\uace0\uc790 \ud558\uc2e0\ub2e4\uba74 <a href=\"https:\/\/awskrug.github.io\/amplify-photo-gallery-workshop\/\">\ub2e8\uacc4\ubcc4 \uc2e4\uc2b5 \uacfc\uc815<\/a>\uc744 \uc0b4\ud3b4 \ubcf4\uc2dc\uae30 \ubc14\ub78d\ub2c8\ub2e4.<\/em><\/p>\n<p>Source: <a href=\"https:\/\/aws.amazon.com\/ko\/blogs\/korea\/building-an-android-app-with-aws-amplify-part-2\/\" target=\"_blank\" rel=\"noopener noreferrer\">AWS Amplify\ub97c \uc774\uc6a9\ud55c Android \uc571 \uac1c\ubc1c \uc2e4\uc2b5 \u2013 2\ubd80<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"mh-excerpt\"><p>AWS Amplify\ub97c \uc774\uc6a9\ud55c Android \uc571 \uac1c\ubc1c \uc2e4\uc2b5 \u2013 2\ubd80 \uc774 \uae00\uc740 AWS Amplify \uac1c\ubc1c \ub3c4\uad6c\ub97c \uc0ac\uc6a9\ud558\uc5ec AWS \ud074\ub77c\uc6b0\ub4dc \uae30\ubc18 Android \ubaa8\ubc14\uc77c \uc571\uc744 \uc81c\uc791\ud558\ub294 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc2e4\uc2b5 \uc2dc\ub9ac\uc988\uc785\ub2c8\ub2e4. \uc774 \uae00\uc5d0\uc11c\ub294 1\ubd80\uc5d0 \uc774\uc5b4 Android \uc571\uc5d0 \uace0\uae09 \uae30\ub2a5\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4. \ub2e4\ub8e8\ub294 \ub0b4\uc6a9\uc740 \ub2e4\uc74c\uacfc \uac19\uc2b5\ub2c8\ub2e4. \uae30\ub2a5 \uc5c5\ub370\uc774\ud2b8 : AWS AppSync API \uc624\ud504\ub77c\uc778 \uc9c0\uc6d0 \ub370\uc774\ud130 \ubcc0\uacbd(\ubcc0\ud615)\uc5d0 \ub300\ud55c \uad6c\ub3c5 \uc0ac\uc6a9 Amazon S3\ub97c \ud1b5\ud55c \uac1d\uccb4 \uc2a4\ud1a0\ub9ac\uc9c0 \ud65c\uc131\ud654 \uc0ac\uc804 \uc870\uac74 Android \ud504\ub85c\uc81d\ud2b8\ub97c \uc791\uc131\ud558\ub824\uba74 \uc6cc\ud06c \uc2a4\ud14c\uc774\uc158\uc5d0 Java JDK\uac00 \uc124\uce58\ub418\uc5b4 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4. Android Studio\ub97c \ub2e4\uc6b4\ub85c\ub4dc\ud558\uc5ec \uc124\uce58\ud558\uace0 Android SDK \uad00\ub9ac\uc790\uc5d0\uc11c Android 6.0 SDK(API \ub808\ubca8 23 \uc774\uc0c1)\ub97c \ub2e4\uc6b4\ub85c\ub4dc\ud569\ub2c8\ub2e4. \ub610\ud55c \uc5d0\ubbac\ub808\uc774\ud130 \uc774\ubbf8\uc9c0\ub3c4 \ub2e4\uc6b4\ub85c\ub4dc\ud569\ub2c8\ub2e4. \uc774\ub97c \uc704\ud574 Android Studio\uc5d0\uc11c AVD Manager\ub97c \uc120\ud0dd\ud574\uc57c \ud569\ub2c8\ub2e4. + Create Virtual Device\ub97c \uc120\ud0dd\ud558\uace0 \uc9c0\uce68\uc5d0 \ub530\ub77c \uc124\uce58\ub97c \uc644\ub8cc\ud569\ub2c8\ub2e4. 1 \ubd80\uc5d0\uc11c \uacc4\uc18d 1\ubd80\uc5d0\uc11c \uc6b0\ub9ac\ub294 \uc560\uc644 \ub3d9\ubb3c\uc758 \ubaa9\ub85d\uc744 \ud45c\uc2dc\ud558\uace0 \uc0c8\ub85c\uc6b4 \uc560\uc644 \ub3d9\ubb3c\uc744 \ucd94\uac00\ud560 \uc218 \uc788\ub294 Android \uc571\uc744 \ub9cc\ub4e4\uc5c8\uc2b5\ub2c8\ub2e4. \uc0c8 \uc560\uc644 \ub3d9\ubb3c\uc744 \ucd94\uac00\ud558\uba74 \uc571\uc740 \ub2e4\uc74c\uacfc \uac19\uc740 \ud615\ud0dc\uac00 \ub429\ub2c8\ub2e4. \ucd5c\uc801 \uc5c5\ub370\uc774\ud2b8 \ubc0f \uc624\ud504\ub77c\uc778 \uc9c0\uc6d0 \ucd5c\uc801 \uc5c5\ub370\uc774\ud2b8 <a class=\"mh-excerpt-more\" href=\"https:\/\/jirak.net\/wp\/aws-amplify%eb%a5%bc-%ec%9d%b4%ec%9a%a9%ed%95%9c-android-%ec%95%b1-%ea%b0%9c%eb%b0%9c-%ec%8b%a4%ec%8a%b5-2%eb%b6%80\/\" title=\"AWS Amplify\ub97c \uc774\uc6a9\ud55c Android \uc571 \uac1c\ubc1c \uc2e4\uc2b5 \u2013 2\ubd80\">[ more&#8230; ]<\/a><\/p>\n<\/div>","protected":false},"author":1,"featured_media":31127,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[169],"tags":[656],"class_list":["post-31126","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-news","tag-aws"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts\/31126","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/comments?post=31126"}],"version-history":[{"count":1,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts\/31126\/revisions"}],"predecessor-version":[{"id":31128,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts\/31126\/revisions\/31128"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/media\/31127"}],"wp:attachment":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/media?parent=31126"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/categories?post=31126"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/tags?post=31126"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}