Androidのアプリのフィードバックをslackに投稿する

# Androidアプリのフィードバック
最近グッドパッチさんから良い感じのサービスが出ているのを発見した。

[Balto](https://www.balto.io/ja/)

アプリのフィードバックを素早く投稿して、エンジニアに共有することができる。また投稿はgithubに投稿されissue化されるのでかなり便利。でも**Androidは5.0**以上らしい・・・（[Baltoを少しだけ触ってみた](http://qiita.com/gupuru/items/4f3850b0a4780964676a)）

おしい。4系はまだシェア的に10%ほどいて、切れないのでとりあえずSlackにpostするのを作った。
Activityのキャプチャをとって、postするやつだ。



## キャプチャしてSlackに投稿する

### まずキャプチャする。

```kotlin

fun AppCompatActivity.captureScreen():Bitmap{
    val rootView = window.decorView.rootView
    val screenView = rootView.getRootView()
    screenView.setDrawingCacheEnabled(true)
    val bitmap = Bitmap.createBitmap(screenView.getDrawingCache())
    screenView.setDrawingCacheEnabled(false)
    return bitmap
}
```

ActivityのExtensionです。

### Fragment

```xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@android:color/white"
    android:orientation="vertical"
    android:clickable="true"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
            <LinearLayout
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp"
                android:textSize="17dp"
                android:text="＊どこからでも送信できます！！"/>
            <RadioGroup
                android:orientation="horizontal"
                android:id="@+id/buttongroup_feedback"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <RadioButton
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="バグ"
                    android:id="@+id/radioButton" />

                <RadioButton
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="改善"
                    android:layout_gravity="right" />

                <RadioButton
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="イイね！"
                    android:layout_gravity="center_horizontal" />
                <Button
                    android:id="@+id/button_feedback"
                    android:layout_marginLeft="50dp"
                    android:layout_gravity="right"
                    android:text="送信！！！"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content" />

            </RadioGroup>

            <EditText
                android:padding="10dp"
                android:hint="フィードバックを入力してください"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:id="@+id/editText_feedback" />

            <ImageView
                android:id="@+id/imageView_feedback"
                android:layout_width="match_parent"
                android:layout_height="300dp" />
        </LinearLayout>

    </ScrollView>
</LinearLayout>
```

```kotlin
import android.graphics.Bitmap
import android.os.Bundle
import android.support.v4.app.Fragment
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import com.google.repacked.apache.commons.io.FileUtils
import jp.co.rarejob.R
import jp.co.rarejob.lib.util.AlertDialogUtil
import okhttp3.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer

/**
 * Created by kentaro.haneda on 16/12/21.
 */
class FeedbackFragment(var bitmap:Bitmap) : Fragment() {

    lateinit var categoryButtonGroup:RadioGroup
    lateinit var feedbacEditText: EditText
    lateinit var captureImage:ImageView

    var selectedCategoryName = "その他"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onDestroy() {
        super.onDestroy()
    }

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val view = inflater!!.inflate(R.layout.fragment_feedback, container, false)
        captureImage = view.findViewById(R.id.imageView_feedback) as ImageView
        captureImage.setImageBitmap(bitmap)

        (view.findViewById(R.id.buttongroup_feedback) as RadioGroup).setOnCheckedChangeListener { radioGroup, i ->
            when(i){
                0 -> { selectedCategoryName = "バグ" }
                1 -> { selectedCategoryName = "改善" }
                2 -> { selectedCategoryName = "イイね" }
            }
        }
        (view.findViewById(R.id.button_feedback) as Button).setOnClickListener {
            postToSlack()
        }
        feedbacEditText = view.findViewById(R.id.editText_feedback) as EditText
        feedbacEditText.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable) {
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                (view.findViewById(R.id.button_feedback) as Button).isEnabled = feedbacEditText.text.toString().count() > 0
            }
        })

        return view
    }

    fun postToSlack(){

        val byteArrayOutputStream = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
        val requestBodyBitmap = RequestBody.create(MediaType.parse("image/png"), byteArrayOutputStream.toByteArray())

        val service = SlackServiceGenerator.createService(SlackFeedbackAPI::class.java)

        // set post file name
        val body = MultipartBody.Part.createFormData("file", "feedback_image.png", requestBodyBitmap)

        // for slack
        // get from here https://XXXXXXXX.slack.com/apps/new/
        val token = RequestBody.create(
                MediaType.parse("multipart/form-data"), "XXXXXXXXXXXXX")
        val channel = RequestBody.create(
                MediaType.parse("multipart/form-data"), "feedback")
        val title = RequestBody.create(
                MediaType.parse("multipart/form-data"), selectedCategoryName)
        val comment = RequestBody.create(
                MediaType.parse("multipart/form-data"), feedbacEditText.text.toString())
        val pretty = RequestBody.create(
                MediaType.parse("multipart/form-data"), "1")

        // finally, execute the request
        val call = service.upload(token, channel, pretty, title, comment, body)
        call.enqueue(object : Callback<ResponseBody> {
            override fun onResponse(call: Call<ResponseBody>,
                                    response: Response<ResponseBody>) {
                Log.v("Upload", "success")
            }

            override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                Log.e("Upload error:", t.message)
            }
        })
    }
}

interface SlackFeedbackAPI{
    @Multipart
    @POST("/api/files.upload")
    fun upload(@Part("token") version:RequestBody,
               @Part("channels") name:RequestBody,
               @Part("pretty") category:RequestBody,
               @Part("title") title:RequestBody,
               @Part("initial_comment") comment:RequestBody,
               @Part file: MultipartBody.Part ): Call<ResponseBody>
}

object SlackServiceGenerator {

    val API_BASE_URL = "https://slack.com"

    private val httpClient = OkHttpClient.Builder()

    private val builder = Retrofit.Builder().baseUrl(API_BASE_URL).addConverterFactory(GsonConverterFactory.create())

    fun <S> createService(serviceClass: Class<S>): S {
        val retrofit = builder.client(httpClient.build()).build()
        return retrofit.create(serviceClass)
    }
}

```

retrofit2とokhttpを使っています。トークンは https://XXXXXXXX.slack.com/apps/new/ こんな感じのURLで発行されると思います。

Baltoが4k対応したら、iOSもまとめたい！

[コードはこちらにも](https://gist.github.com/jumbo-in-Jap/17f4e1d827a04a45c38eae17e6a1032f)