ããã«ã¡ã¯ã
é£è¼ã·ãªã¼ãº4æ¥ç®ãæ
å½ãã¾ããè²·ç©äºæ¥é¨ Androidã¨ã³ã¸ãã¢ã®éç°(twitter: @_litmon_
)ã§ãã
âââ以åã®3æ¥åã®ã¨ã³ããªã¯ãã¡ãããåç §ãã ããâââ
- vol.1 ã¯ãã¯ããããã¼ãã«ãããå®ä¸çã§ã®é éãæèãã注æã®æ¤è¨¼å¦ç
- vol.2 ã¯ãã¯ããããã¼ãiOSã¢ããªã楽ããæ°è¦éçºãã話
- vol.3 1æã®ã©ãã«ã®åããã«ã¯ã1人ã®ã¦ã¼ã¶ããã
è²·ç©äºæ¥é¨ã§ã¯ãã¯ãã¯ãããã®çé®®é£åECãµã¼ãã¹ãã¯ãã¯ããããã¼ããã®éçºãè¡ã£ã¦ãã¾ãã ä»æ¥ã¯ãå æ¥ãªãªã¼ã¹ããã°ããã®ã¯ãã¯ããããã¼ãAndroidã¢ããªãéçºããä¸ã§ãç»é¢å®è£ ã®å·¥å¤«ã«ã¤ãã¦ç´¹ä»ãããã¨æãã¾ãã
ã¯ãã¯ããããã¼ãAndroidã¢ããªã¯ãã¡ããããã¦ã³ãã¼ãã§ãã¾ãããã²å®éã«è§¦ããªããè¨äºãèªãã§ã¿ã¦ãã ããã play.google.com
ã¯ãã¯ããããã¼ãAndroidã¢ããªã®ç»é¢å®è£
ã¯ãã¯ããããã¼ãAndroidã¢ããªã®ä¸»ãªç»é¢ã¯ã大ããåãã¦3ã¤ã«åé¡ããã¾ãããã®åé¡ã¯ãå¤å°ã®éãã¯ããã©ä¸è¬çãªAndroidã¢ããªã«å¯¾ãã¦ãåæ§ã®ãã¨ãè¨ããã®ã§ã¯ãªãã§ããããã
- ä¸è¦§ç»é¢: ãã¼ã¿ã®ãªã¹ããä¸è¦§è¡¨ç¤ºããç»é¢
- 詳細ç»é¢: ä¸è¦§ç»é¢ã®ç¹å®ã®ãã¼ã¿ã«å¯¾ãã¦è©³ç´°ã表示ããç»é¢
- å ¥åç»é¢: ãã¼ã¿ãç»é²ããã追å ãããããããã«å ¥åãè¡ãç»é¢
ä¸è¦§ç»é¢ | 詳細ç»é¢ | å ¥åç»é¢ |
---|---|---|
ç¾ä»£ã®Androidã¢ããªéçºã«ããã¦ãä¸è¦§ç»é¢ã«ã¯RecyclerViewã使ãããã®ãä¸è¬çã§ããRecyclerViewã¯ãåä¸ã®ã¬ã¤ã¢ã¦ããè¤æ°æã¤ä¸è¦§ç»é¢ã«ããã¦é常ã«é«ãããã©ã¼ãã³ã¹ãçºæ®ããViewã§ãããAdapterãViewHolderãªã©å®è£ ãããã®ãå¤ããè¥å¹²æ±ãã«ããã®ãé£ç¹ã§ãã
詳細ç»é¢ã®å®è£ ã«é¢ãã¦ã¯ãã¹ãã¼ããã©ã³ã®ãã£ã¹ãã¬ã¤ã¯ç¸¦ã«é·ããã¹ã¯ãã¼ã«ã®æ¹åã縦ã«ãªãã¢ããªãå¤ãããã ScrollViewãNestedScrollViewã使ã£ã¦ãã®ä¸ã«ã¬ã¤ã¢ã¦ããçµãã®ãä¸è¬çã ã¨æãã¾ãã
å ¥åç»é¢ã«ã¯ãEditTextãCheckBoxãªã©ãå©ç¨ãã¦å ¥åæ¬ãç¨æããã¨æãã¾ããã¾ããå ¥åé ç®ãå¤ããªã£ãå ´åã«ã¯è©³ç´°ç»é¢åæ§ã«ScrollViewãªã©ã使ã£ã¦ã¹ã¯ãã¼ã«åºæ¥ãããã«å®è£ ãããã¨ãå¤ãã®ã§ã¯ãªãã§ããããã
ã¯ãã¯ããããã¼ãAndroidã¢ããªã§ã¯ãä¸ã®ä¾ã«æ¼ããä¸è¦§ç»é¢ã§ã¯RecyclerViewã使ãã詳細ç»é¢ã§ã¯ScrollViewã使ãã¨ããã¹ã¿ã¤ã«ãåã£ã¦ããã®ã§ããã ãã®ã¬ã¤ã¢ã¦ãææ³ã§éçºãé²ãã¦ããã®ã大å¤ã«ãªã£ã¦ããã¾ããã ç¹ã«ã詳細ç»é¢ã®å®è£ ãScrollViewã§è¡ã£ã¦ãããã¨ã«é¢ãã¦é常ã«è¦ããæããããä¾ãç´¹ä»ãã¾ãã
ã¬ã¤ã¢ã¦ãã¨ãã£ã¿ã§ã®ãã¬ãã¥ã¼ãæ´»ç¨ãã¥ãã
ScrollViewã使ã£ã¦ç¸¦ã«ä¼¸ã³ãã¬ã¤ã¢ã¦ããçµãå ´åã縦ã«ä¼¸ã³ãã°ä¼¸ã³ãã»ã©ã¬ã¤ã¢ã¦ãã¨ãã£ã¿ã®ãã¬ãã¥ã¼æ©è½ã使ãã«ãããªã£ã¦ããã¾ãã ã¾ããã¬ã¤ã¢ã¦ããã¡ã¤ã«ãè¥å¤§åããé常ã«è¦éãã®æªãå®è£ ã«ãªããã¡ã§ãã
詳細ç»é¢ã®å®è£ ãActivity, Fragmentã«éä¸ãã¦è¥å¤§åãããã
RecyclerViewã使ãã¨ãViewã®å®è£ ã®å¤§åã¯ViewHolderã¯ã©ã¹ã«åé¢ãããã¨ãåºæ¥ã¾ãã ãããã詳細ç»é¢ã§ã¯ScrollViewã使ã£ã¦ããããããã¼ã¿ãViewã«ç´ä»ããå¦çãã©ããã¦ãActivity, Fragmentå ã«æ¸ããã¨ãå¤ããªãã¾ãã DataBindingãMVVMã¢ã¼ããã¯ãã£ãªã©ã使ã£ã¦Viewã®å®è£ ãActivity, Fragmentããåé¢ããææ³ãªã©ãããã¾ãããããã¸ã§ã¯ãã«ãã£ã¦ã¯ãã¾ãé©ããªãã±ã¼ã¹ãããã§ãããã ã¾ããRecyclerViewã使ãä¸è¦§ç»é¢ã¨å®è£ å·®ç°ãåºã¦ãã¾ããå¦çã®å ±éåãªã©ãé£ãããªã£ã¦ãã¾ãã¾ãã
ãªã«ããå®è£ ãã¦ãã¦è¦ãã
詳細ç»é¢ã®ãããªè¤éãªã¬ã¤ã¢ã¦ãæ§æã1ã¤ã®ã¬ã¤ã¢ã¦ããã¡ã¤ã«ã«å¯¾ãã¦ä¸ããé ã«å®è£ ãã¦ããã®ã¯ãç²¾ç¥çã«ãè¦ãããã®ãããã¾ãã é·ããªãã°ãªãã»ã©ã¬ã¤ã¢ã¦ãã¨ãã£ã¿, XML両æ¹ã®ç·¨éä½æ¥ãé£ãããªã£ã¦ãããããç´°ããåä½ã§ã¬ã¤ã¢ã¦ããåå²ã§ããRecyclerViewã®ãããªä»çµã¿ã欲ãããªã£ã¦ãã¾ãã
includeã¿ã°ï¼ç¥ããªãåã§ããâ¦â¦
ãã¹ã¦ã®ç»é¢ãRecyclerViewåãã
ããã§ãRecyclerViewããã¾ã使ããã¨ã§è©³ç´°ç»é¢ããã¾ãçµã¿ç«ã¦ããã¨ãåºæ¥ãã®ã§ã¯ï¼ã¨èãã¾ãããRecyclerViewã¯ãã¬ã¤ã¢ã¦ããè¡ãã¨ã«åå²ãã¦ä½æãããã¨ãåºæ¥ãããViewHolderã¸Viewã®å®è£ ãå§è²åºæ¥ããããActivityãFragmentã®è¥å¤§åãé²ããã¨ãåºæ¥ã¾ãã ãã ãRecyclerViewã®å®è£ ã«ã¯ãAdapterã¨ViewHolderã®å®è£ ãå¿ è¦ã§ãç¹ã«è¤éãªç»é¢ã«ãªãã»ã©Adapterã®å®è£ ãé¢åã«ãªã£ã¦ããã¾ãã
class DeliveryDetailOrderItemsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun getItemCount(): Int = 1 + 1 + items.size + 1 override fun getItemViewType(position: Int): Int { if (position == 0) { return R.layout.item_delivery_detail_header_label } if (position == 1) { return R.layout.item_delivery_detail_order_item_note } if (position == itemCount - 1) { return R.layout.item_delivery_detail_order_item_footer } return R.layout.item_delivery_detail_order_item } }
RecyclerViewã§åãåã詳細ç»é¢ãå®è£ ããã¨ãã®ä¸é¨ãæç²ãã¦ãã¾ããã 表示ããpositionã«å¿ãã¦ããããã®itemViewTypeãå¤ããå¿ è¦ãããã®ã§ãããå ¨ãç´æçã§ã¯ãªããé ã使ã£ã¦å®è£ ããå¿ è¦ãããã¾ãã ã¾ããViewã追å ãããã¨ããå¤æ´ããã£ãã¨ãã«ãä»ã®é¨åã«ãå½±é¿ãåºãå ´åãããã®ã§ãä¿å®æ§ãé«ãããã¾ããã
ãã¹ã¦ã®ç»é¢ã§ãã®ãããªè¤éãªRecyclerView.Adapterãå®è£ ããã®ã¯æ°ãæ» å ¥ãã¾ãããç¾å®çã§ã¯ããã¾ããã ããããèªã¿è¾¼ãã ãã¼ã¿ã«å¿ãã¦è¡¨ç¤ºãå¤ããªããã°ãããªããã¨ãªãã¨ããé¢åã«ãªãã®ã¯å¿ è³ã§ãã ãã®ãããRecyclerView.Adapterã®å®è£ ãç°¡ç´ ã«è¡ãããã®ã©ã¤ãã©ãªãå°å ¥ãããã¨ã«ãã¾ããã
Groupieã使ã
RecyclerView.Adapterã®é¢åãªå®è£ ã便å©ã«ãã¦ãããã©ã¤ãã©ãªã¯å··ã«ããã¤ãããã¾ãããä»åã¯Groupieã使ããã¨ã«ãã¾ããã åæ§ã®ä»çµã¿ãæã¤Epoxyã¨ããã©ã¤ãã©ãªãåè£ã«ä¸ãã£ã¦ãã¾ããããå¤æã®æ±ºãæã¨ãªã£ãã®ã¯ä»¥ä¸ã®ç¹ã§ããã
- Epoxyã¯annotationProcessorã使ã£ãã³ã¼ãçææ©æ§ãåãã£ã¦ãããDataBindingã¨é£æºãããã¨ã¨ã¦ã便å©ã ããã¯ãã¯ããããã¼ãAndroidã¢ããªã§ã¯DataBindingã使ã£ã¦ããããªã¼ãã¼ã¹ããã¯ã ã£ã
- Groupieã¯RecyclerView.Adapterãç½®ãæããã ãã§ä½¿ããã®ã§é常ã«ç°¡ç´ ã§ãä»åã®ã¦ã¼ã¹ã±ã¼ã¹ã«ããããã¦ãã
ä¾ãã°ãGroupieã使ã£ã¦ä¸è¦§ç»é¢ã®ãããªãã¼ã¿ã®ãªã¹ãã表示ãããããã«å¿ è¦ãªã³ã¼ãã¯ä»¥ä¸ã§ãã
data class Data(val name: String) class DataItem(val data: Data) : Item<ViewHolder>() { override fun getLayoutId(): Int = R.layout.item_data override fun bind(viewHolder: ViewHolder, position: Int) { viewHolder.root.name.text = name } } val items = listOf<Data>() // APIããè¿ã£ã¦ãããªã¹ãã¨ãã val adapter = GroupAdapter<ViewHolder>() recycler_view.adapter = adapter adapter.update(mutableListOf<Group>().apply { items.forEach { add(DataItem(it)) } })
ãã£ãããã ãã§ããã¨ã¦ãç°¡åã§ããã
詳細ç»é¢ã®å ´åããã¼ã¿ã®æç¡ã§è¡¨ç¤ºãã/ããªããåãæ¿ããå¿ è¦ããã£ãããã¾ãããã ä¾ãã°ãã¯ãã¯ããããã¼ãAndroidã¢ããªã®ã«ã¼ãç»é¢ã§ã¯ãã«ã¼ãã«ååã追å ããã¦ããªãå ´åã¨è¿½å ããã¦ããå ´åã§è¡¨ç¤ºãç°ãªãã¾ãã
ãã®ãããªã¬ã¤ã¢ã¦ãã«ãªãããã«RecyclerView.Adapterãèªåã§å®è£
ãããã¨ããã¨ã getItemViewType()
ã¡ã½ããã®å®è£
ã«è¦ãã姿ãç°¡åã«æ³åã§ãã¾ããâ¦â¦çµ¶å¯¾ã«ããããããã¾ããã
ãããããããGroupieã§è¡¨ç¾ããã¨ä»¥ä¸ã®ããã«ç°¡åã«è¡¨ç¾ãããã¨ãã§ãã¾ããç°¡ç¥åã®ããã表示ãå¤ããé¨åã®ã¿ã表ç¾ãã¾ãã
class Cart( val products: List<Product> ) { class Product } class CartEmptyItem : Item<ViewHolder>() { /* çç¥ */ } class CartProductItem(val product: Cart.Product) : Item<ViewHolder>() { /* çç¥ */ } val cart = Cart() // APIããè¿ã£ã¦ããã«ã¼ããªãã¸ã§ã¯ã val adapter = GroupAdapter<ViewHolder>() recycler_view.adapter = adapter adapter.update(mutableListOf<Group>().apply { if (cart.products.isEmpty()) { add(CartEmptyItem()) // ååã追å ããã¦ããªãæ¨ã表示ãã } else { cart.products.forEach { add(CartProductItem(it)) // ã«ã¼ãã®ååããªã¹ãã§è¡¨ç¤ºãã } } })
é常ã«ã³ã³ãã¯ããªå®è£
ã«åã¾ãã¾ãã
åè¿°ã®ä¾ã¨ãããã¦è¦ãã¨ãgetItemViewType()
ãå®è£
ããã®ã«æ¯ã¹ã¦ç´æçã«ãªããã¨ãç解ã§ããã¨æãã¾ãã
LiveDataã¨çµã¿åããã¦ä½¿ã
LiveDataã¨çµã¿åããã¦ä½¿ãå ´åãã¨ã¦ãç°¡åã§ããFragmentå ã§ä½¿ããã¨ãä¾ã«æãã¦ã¿ã¾ãããã
class CartFragment : Fragment() { class CartViewModel : ViewModel { val data = MutableLiveData<Cart>() } val viewModel by lazy { ViewModelProviders.of(this).get<CartViewModel>() } val adapter = GroupAdapter<ViewHolder>() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_cart, container, false) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) recycler_view.adapter = adapter viewModel.data.observe(this, Observer { it?.let { cart -> adapter.update(mutableListOf<Group>().apply { cart.products.forEach { product -> CartProductItem(product) } }) } }) } }
é常ã«ç°¡åã§ããã Groupieã¯updateæã«å é¨ã§DiffUtilsã使ã£ã¦å·®åæ´æ°ãè¡ã£ã¦ããããããAPIãªã¯ã¨ã¹ããè¡ã£ãçµæãLiveDataã§æµãã ãã§ç°¡åã«æ´æ°ãåºæ¥ã¾ãã
ãã®éãGroupieã®Itemã«å¯¾ãã¦ä»¥ä¸ã®2ç¹ãè¦ã¦ããå¿ è¦ãããã¾ãã
- id ãåä¸ã«ãªãããã«ãªã£ã¦ããã
- equals ãå®è£ ããã¦ããã
idã®è¨å®ã¯ãgetId()
ã¡ã½ãããoverrideããã¨è¯ãã§ãããã
ãããã¯ãItemã¯ã©ã¹ã®ã³ã³ã¹ãã©ã¯ã¿å¼æ°ã«idã渡ããã¨ãåºæ¥ã¾ãã
equals()
ã¡ã½ããã®å®è£
ã¯ãKotlinãªãã°data classã§ç°¡åã«å®è£
ãããã¨ãåºæ¥ã¾ãã
ã¾ããå¼æ°ãæããªããããªItemã§ãç¹ã«ä¸ã®å
容ãå¤ãããªããããªå ´åã¯èªåã§å®è£
ãã¦ãã¾ã£ã¦ãè¯ãã§ãããã
data class CartProductItem(val product: Cart.Product) : Item<ViewHolder>() { override fun getLayoutId(): Int = R.layout.item_data override fun getId(): Int = product.id override fun bind(viewHolder: ViewHolder, position: Int) { viewHolder.root.name.text = name } } // idãã³ã³ã¹ãã©ã¯ã¿ã§æå® class CartEmptyItem : Item<ViewHolder>(0) { override fun getLayoutId(): Int = R.layout.item_data override fun hashCode(): Int = 0 // åãItemãªãåãã¨å¤å®ãã¦è¯ã override fun equals(other: Object): Boolean = (other instanceOf CartEmptyItem) override fun bind(viewHolder: ViewHolder, position: Int) { // ignore } }
ãããã®è¨å®ããã¾ããã£ã¦ããªãå ´åãæ´æ°ãããã¨ãã«ç¡é§ãªã¢ãã¡ã¼ã·ã§ã³ãèµ°ã£ã¦ãã¾ããããã§ããã ãå ¨ã¦ã®Itemã«å®è£ ãã¦ãããã¨ããªã¹ã¹ã¡ãã¾ãã
å®éã«ã¯ãã¯ããããã¼ãAndroidã¢ããªã§ã¯ãã»ã¼ãã¹ã¦ã®ç»é¢ããã®æ§æãåã£ã¦å®è£ ãã¦ãã¦ãç»é¢å転æãFragmentã®Viewåçæã«ãåé¡ãªãç¶æ ãåç¾ãã¦ãããã®ã§ã¨ã¦ã便å©ãªæ§æã«ãªã£ã¦ãã¾ãã
Groupieã使ããã¨ã§è¯ããªã£ãç¹
Groupieã使ããã¨ã§ãRecyclerViewã®é¢åãªå®è£ ãç°¡ç¥åã§ãããã¤ãã¹ã¦ã®ç»é¢ã®å®è£ ãå®ååãããã¨ãåºæ¥ã¾ããã ããã«ããã以ä¸ã®ãããªå¹æãçã¾ãã¾ããã
- Fragmentã®å®è£ ããã¹ã¦ã®ç»é¢ã§ã»ã¼å®å½¢ååºæ¥ããããç²¾ç¥çã«æ¥½ã«å®è£ ã§ããããã«ãªãããã¤å¦çã®å ±éåãç°¡åã«ãªã£ã
- RecyclerView.Adapterã«æ¯ã¹ã¦ãè¤éãªã¬ã¤ã¢ã¦ããçµãã®ãé常ã«ç°¡åãªã®ã§ãå®è£ å·¥æ°ãå¤§å¹ ã«åæ¸ãããã¨ãåºæ¥ã
- ã¬ã¤ã¢ã¦ããå¼·å¶çã«Itemåä½ã«ãªããããã·ã³ãã«ã«ã¬ã¤ã¢ã¦ããä½æãããã¨ãåºæ¥ãããã«ãªã£ã
1ã¤ç®ã®å¦çã®å ±éåã«ã¯ãä¾ãã°ã¨ã©ã¼ç»é¢ãæãããã¾ãã èªã¿è¾¼ã¿ã¨ã©ã¼æã®ç»é¢è¡¨ç¤ºãGroupieã®Itemã§ç¨æãããã¨ã§ãé常ã«ç°¡åã«å ¨ã¦ã®ç»é¢ã§åä¸ã®ã¨ã©ã¼ç»é¢ãç¨æãããã¨ãåºæ¥ã¾ãã
class ErrorItem(val throwable: Throwable): Item<ViewHolder>() { /* çç¥ */ } apiRequest() .onSuccess { data -> adapter.update(mutableListOf<Group>().apply { add(DataItem(data)) }) } .onError { throwable -> adapter.update(mutableListOf<Group>().apply { add(ErrorItem(throwable)) }) }
ã¾ããã¢ããªå ã®Itemã®éã«è¡¨ç¤ºããã¦ãã罫ç·ããRecyclerViewã®ItemDecorationã使ããã¨ã§ã¢ããªå ¨ä½ã§ç°¡åã«å ±éåãããã¨ãåºæ¥ã¾ããã RecyclerViewã«LinearLayoutManagerã¨ãããã¦dividerãè¨å®ãããã¨ãã¨ã¦ãå¤ãã£ããããRecyclerViewã«ä»¥ä¸ã®æ¡å¼µé¢æ°ãå®è£ ãã¦ä½¿ãããã«ãã¦ãã¾ãã
fun RecyclerView.applyLinearLayoutManager(orientation: Int = RecyclerView.VERTICAL, withDivider: Boolean = true) { layoutManager = LinearLayoutManager(context).apply { this.orientation = orientation } if (withDivider) { addItemDecoration(DividerItemDecoration(context, orientation).apply { ContextCompat.getDrawable(context, R.drawable.border)?.let(this::setDrawable) }) } }
Groupieã§é£ããã£ãç¹
Groupieã使ã£ã¦ãã¦ãé£ããã£ãç¹ãããã¤ãããã¾ãã ä¾ãã°ãåãåãå ´æã®è©³ç´°ç»é¢ã«ã¯å°å³ã表示ãã¦ããã®ã§ãããMapViewã«ã¯MapFragmentãã¢ã¿ããããå¿ è¦ãããã¾ãã åç´ã«addããã ãã®å®è£ ã ã¨ãã¹ã¯ãã¼ã«ãã¦æ»ã£ã¦ããã¨ãã«ã¯ã©ãã·ã¥ãã¦ãã¾ãã®ã§ãunbindæã«Fragmentãåãé¤ãå¿ è¦ãããã¾ããã è¦èã®çã§ãããç¾ç¶ã¯ä»¥ä¸ã®ãããªå®è£ ã«ãªã£ã¦ãã¾ãã
data class SelectAreaDetailMapItem( val item: Location, val mapFragment: SupportMapFragment, val fragmentManager: FragmentManager ) : Item<ViewHolder>() { override fun getLayout(): Int = R.layout.item_select_area_detail_header override fun getId(): Long = layout.toLong() override fun bind(viewHolder: ViewHolder, position: Int) { val markerPosition = LatLng(item.latitude, item.longitude) fragmentManager.beginTransaction() .add(R.id.item_select_area_detail_map, mapFragment) .commit() mapFragment.getMapAsync { map -> map.addMarker(MarkerOptions().position(markerPosition)) map.moveCamera(CameraUpdateFactory.newLatLng(markerPosition)) map.setMinZoomPreference(15f) } } override fun unbind(holder: ViewHolder) { super.unbind(holder) fragmentManager.beginTransaction() .remove(mapFragment) .commit() } }
ãã¹ã¦ã®ç»é¢ã§Groupieã使ããã¨ã§å®è£ ãç°¡åã«ãªã£ããã¢ããªå ¨ä½ã§å¦çãå ±éååºæ¥ãã¨ããã¡ãªããã¯ããã¾ãããããããã風ã«æ±ãã«å°ãã±ã¼ã¹ããããããç¨æ³ç¨éãå®ã£ã¦æ£ããã使ããã ããã
ã¾ã¨ã
- Androidã¢ããªéçºã«ããã¦ä¸»è¦ãªç»é¢ã¯ã ãããRecyclerViewã§è¡¨ç¾ã§ãã
- Groupieã使ãã¨å®è£ ãç°¡åã«ãªã£ã¦æé«
- é£ããç»é¢ãããã®ã§é©æé©æã§ä½¿ãåããã
ãããã
4/24(æ¨)ã«ãè²·ç©äºæ¥é¨ã®ã¨ã³ã¸ãã¢ã«ããçºè¡¨ã¨ã¨ã³ã¸ãã¢ã¨ã®ãã¼ãã¢ãããéå¬ãã¾ãï¼ï¼ï¼ cookpad.connpass.com
ããã§ã¯ãä»åèªããªãã£ãAndroidã¢ããªã®æè¡çãªé¨åãç´¹ä»ãã¦ãããã¨æãã¾ãã å®éã®ã½ã¼ã¹ã³ã¼ããè¦ãããâ¦â¦ããããããã¾ãããã ãã²ãã²ï¼ï¼ãèå³ã®ããæ¹ã¯åå ãå¾ ã¡ãã¦ãã¾ãï¼