Внутренний сервер RXJava HTTP 500 - PullRequest
0 голосов
/ 20 ноября 2018

Я преобразовал учебник RAVI TAMADA по RxJava из Java в Kotlin Сеть Android RxJava с модернизацией, приложение Gson - Notes .При тестировании приложения только при первом сетевом вызове я получаю внутреннюю ошибку HTTP 500 (com.jakewharton.retrofit2.adapter.rxjava2.HttpException: внутренняя ошибка сервера HTTP 500).Это для первого сетевого вызова registerUsers Что касается преобразования, я сделал все по книге.

Я включил кодовую базу для следующих классов

MainActivity

class MainActivity : AppCompatActivity()
{

    lateinit var apiService: ApiService
    var disposable = CompositeDisposable()
    lateinit var mAdapter: NotesAdapter
    var noteList = ArrayList<Note>()

    companion object
    {
        val TAG = MainActivity::class.java.simpleName;

    }

    @BindView(R.id.coordinator_layout) var coordinatorLayout: CoordinatorLayout? = null

    @BindView(R.id.recycler_view)  var recyclerView: RecyclerView? = null

    @BindView(R.id.txt_empty_notes_view) var noNotesView: TextView? = null

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

        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        val toolbar = findViewById<Toolbar>(R.id.toolbar)
        toolbar.setTitle(getString(R.string.activity_title_home))
        setSupportActionBar(toolbar)

        fab.setOnClickListener { view ->

            showNoteDialog(false, null, -1);

        }

        // white background notification bar
        whiteNotificationBar(fab);

        apiService = ApiClient.getClient(getApplicationContext())?.create(ApiService::class.java)!!

        mAdapter = NotesAdapter(this, noteList)
        var mLayoutManager = LinearLayoutManager(getApplicationContext());
        recyclerView?.setLayoutManager(mLayoutManager);
        recyclerView?.setItemAnimator(DefaultItemAnimator());
        recyclerView?.addItemDecoration(MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL, 16));
        recyclerView?.setAdapter(mAdapter);

        /**
         * On long press on RecyclerView item, open alert dialog
         * with options to choose
         * Edit and Delete
         * */
        recyclerView?.addOnItemTouchListener(RecyclerTouchListener(this, recyclerView!!, object : RecyclerTouchListener.ClickListener
        {

            override fun onClick(view: View, position: Int)
            {
            }

            override fun onLongClick(view: View, position: Int)
            {
                showActionsDialog(position);
            }
        }))

        /**
         * Check for stored Api Key in shared preferences
         * If not present, make api call to register the user
         * This will be executed when app is installed for the first time
         * or data is cleared from settings
         * */

        var test = PrefUtils.getApiKey(this)
        if (TextUtils.isEmpty(PrefUtils?.getApiKey(this)))
        {
            registerUser();
        } else
        {
            // user is already registered, fetch all notes
            fetchAllNotes();
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean
    {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean
    {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        return when (item.itemId)
        {
            R.id.action_settings -> true
            else -> super.onOptionsItemSelected(item)
        }
    }


    /**
     * Registering new user
     * sending unique id as device identification
     * https://developer.android.com/training/articles/user-data-ids.html
     */
    private fun registerUser()
    {
        // unique id to identify the device
        val uniqueId = UUID.randomUUID().toString()

        disposable
            .add(apiService.register(uniqueId)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(
            object : DisposableSingleObserver<User>()
            {
                override fun onSuccess(user: User)
                {
                    // Storing user API Key in preferences
                    user.apiKey?.let { PrefUtils.storeApiKey(applicationContext, it) }

                    Toast.makeText(applicationContext,
                        "Device is registered successfully! ApiKey: " + PrefUtils.getApiKey(applicationContext),
                        Toast.LENGTH_LONG).show()
                }

                override fun onError(e: Throwable)
                {
                    Log.e(TAG, "onError: " + e.message)
                    showError(e)
                }
            }))
    }

    /**
     * Fetching all notes from api
     * The received items will be in random order
     * map() operator is used to sort the items in descending order by Id
     */
    fun fetchAllNotes()
    {
        disposable.add(apiService.fetchAllNotes().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).map(
            object : io.reactivex.functions.Function<List<Note>, List<Note>>
            {
                override fun apply(notes: List<Note>): List<Note>
                {
                    Collections.sort(notes, object : Comparator<Note>
                    {
                        override fun compare(n1: Note?, n2: Note?): Int
                        {
                            return n2!!.id - n1!!.id;

                        }
                    })
                    return notes
                }
            }).subscribeWith(object : DisposableSingleObserver<List<Note>>()
        {
            override fun onSuccess(notes: List<Note>)
            {
                noteList.clear();
                noteList.addAll(notes);
                mAdapter.notifyDataSetChanged();

                toggleEmptyNotes();
            }

            override fun onError(e: Throwable)
            {
                Log.e(TAG, "onError: " + e.message);
                showError(e);


            }
        }))
    }


    /**
     * Creating new note
     */
    private fun createNote(note: String)
    {
        disposable.add(apiService.createNote(note)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeWith(
            object : DisposableSingleObserver<Note>()
            {

                override fun onSuccess(note: Note)
                {
                    if (!TextUtils.isEmpty(note.error))
                    {
                        Toast.makeText(applicationContext, note.error, Toast.LENGTH_LONG).show()
                        return
                    }

                    Log.d(TAG, "new note created: " + note.id + ", " + note.note + ", " + note.timestamp)

                    // Add new item and notify adapter
                    noteList.add(0, note)
                    mAdapter.notifyItemInserted(0)

                    toggleEmptyNotes()
                }

                override fun onError(e: Throwable)
                {
                    Log.e(TAG, "onError: " + e.message)
                    showError(e)
                }
            }))
    }

    /**
     * Updating a note
     */
    private fun updateNote(noteId: Int, note: String, position: Int)
    {
        disposable
            .add(apiService.updateNote(noteId,
            note)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(object :
            DisposableCompletableObserver()
        {
            override fun onComplete()
            {
                Log.d(TAG, "Note updated!")

                val n = noteList.get(position)
                n.note = (note)

                // Update item and notify adapter
                noteList.set(position, n)
                mAdapter.notifyItemChanged(position)
            }

            override fun onError(e: Throwable)
            {
                Log.e(TAG, "onError: " + e.message)
                showError(e)
            }
        }))
    }

    /**
     * Deleting a note
     */
    private fun deleteNote(noteId: Int, position: Int)
    {
        Log.e(TAG, "deleteNote: $noteId, $position")
        disposable
            .add(apiService.deleteNote(noteId)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(
            object : DisposableCompletableObserver()
            {
                override fun onComplete()
                {
                    Log.d(TAG, "Note deleted! $noteId")

                    // Remove and notify adapter about item deletion
                    noteList.removeAt(position)
                    mAdapter.notifyItemRemoved(position)

                    Toast.makeText(this@MainActivity, "Note deleted!", Toast.LENGTH_SHORT).show()

                    toggleEmptyNotes()
                }

                override fun onError(e: Throwable)
                {
                    Log.e(TAG, "onError: " + e.message)
                    showError(e)
                }
            }))
    }

    /**
     * Shows alert dialog with EditText options to enter / edit
     * a note.
     * when shouldUpdate=true, it automatically displays old note and changes the
     * button text to UPDATE
     */
    private fun showNoteDialog(shouldUpdate: Boolean, note: Note?, position: Int)
    {
        val layoutInflaterAndroid = LayoutInflater.from(applicationContext)
        val view = layoutInflaterAndroid.inflate(R.layout.note_dialog, null)

        val alertDialogBuilderUserInput = AlertDialog.Builder(this@MainActivity)
        alertDialogBuilderUserInput.setView(view)

        val inputNote = view.findViewById<EditText>(R.id.note)
        val dialogTitle = view.findViewById<TextView>(R.id.dialog_title)
        dialogTitle.setText(if (!shouldUpdate) getString(R.string.lbl_new_note_title) else getString(R.string.lbl_edit_note_title))

        if (shouldUpdate && note != null)
        {
            inputNote.setText(note.note)
        }
        alertDialogBuilderUserInput.setCancelable(false).setPositiveButton(if (shouldUpdate) "update" else "save",
            DialogInterface.OnClickListener { dialogBox, id -> })
            .setNegativeButton("cancel", DialogInterface.OnClickListener { dialogBox, id -> dialogBox.cancel() })

        val alertDialog = alertDialogBuilderUserInput.create()
        alertDialog.show()

        alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener {
            // Show toast message when no text is entered
            if (TextUtils.isEmpty(inputNote.text.toString()))
            {
                Toast.makeText(this@MainActivity, "Enter note!", Toast.LENGTH_SHORT).show()
                return@OnClickListener
            } else
            {
                alertDialog.dismiss()
            }

            // check if user updating note
            if (shouldUpdate && note != null)
            {
                // update note by it's id
                updateNote(note.id, inputNote.text.toString(), position)
            } else
            {
                // create new note
                createNote(inputNote.text.toString())
            }
        })
    }

    /**
     * Opens dialog with Edit - Delete options
     * Edit - 0
     * Delete - 0
     */
    private fun showActionsDialog(position: Int)
    {
        val colors = arrayOf<CharSequence>("Edit", "Delete")

        val builder = AlertDialog.Builder(this)
        builder.setTitle("Choose option")
        builder.setItems(colors) { dialog, which ->
            if (which == 0)
            {
                showNoteDialog(true, noteList.get(position), position)
            } else
            {
                deleteNote(noteList.get(position).id, position)
            }
        }
        builder.show()
    }

    private fun toggleEmptyNotes()
    {
        if (noteList.size > 0)
        {
            noNotesView?.setVisibility(View.GONE)
        } else
        {
            noNotesView?.setVisibility(View.VISIBLE)
        }
    }


    /**
     * Showing a Snackbar with error message
     * The error body will be in json format
     * {"error": "Error message!"}
     */
    fun showError(e: Throwable)
    {
        var message = ""
        try
        {
            if (e is IOException)
            {
                message = "No internet connection!"
            }
            else (e is HttpException)
            run {
                var error = e as HttpException
                var errorBody = error.response().errorBody().toString()
                var jObj = JSONObject(errorBody)
                message = jObj.getString("error")



            }

        }
        catch (e1: IOException)
        {
            e1.printStackTrace()
        }
        catch (e1: JSONException)
        {
            e1.printStackTrace()
        }
        catch (e1: Exception)
        {
            e1.printStackTrace()
        }

        if (TextUtils.isEmpty(message))
        {
            message = "Unknown error occurred! Check LogCat."
        }

        val snackbar = coordinatorLayout?.let { Snackbar.make(it, message, Snackbar.LENGTH_LONG) }

        val sbView = snackbar?.getView()
        val textView = sbView?.findViewById<TextView>(android.support.design.R.id.snackbar_text)
        textView?.setTextColor(Color.YELLOW)
        snackbar?.show()
    }

    fun whiteNotificationBar(view: View)
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
        {
            var flags = view.getSystemUiVisibility()
            flags = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
            view.setSystemUiVisibility(flags)
            getWindow().setStatusBarColor(Color.WHITE)
        }
    }

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

Интерфейс ApiService

interface  ApiService
{
    // Register new user
    @FormUrlEncoded
    @POST("notes/user/register")
    fun register(@Field("device_id") deviceId: String): Single<User>
    //    Single<User> register(@Field("device_id") String deviceId)

    // Create note
    @FormUrlEncoded
    @POST("notes/new")
    fun createNote(@Field("note") note: String): Single<Note>

    // Fetch all notes
    @GET("notes/all") fun fetchAllNotes(): Single<List<Note>>

    // Update single note
    @FormUrlEncoded
    @PUT("notes/{id}")
    fun updateNote(@Path("id") noteId: Int, @Field("note") note: String): Completable

    // Delete note
    @DELETE("notes/{id}")
    fun deleteNote(@Path("id") noteId: Int): Completable
    }

Класс ApiClient

class ApiClient
{
    companion object
    {
        var retrofit: Retrofit? = null
        var REQUEST_TIMEOUT = 60
        var okHttpClient: OkHttpClient? = null

        fun getClient(context: Context): Retrofit?
        {
            if (okHttpClient == null)
                initOkHttp(context)

            if (retrofit == null)
            {
                retrofit = Retrofit.Builder().baseUrl(BASE_URL).client(okHttpClient)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create()).build()
            }
            return retrofit
        }

        fun initOkHttp(context: Context)
        {
            val httpClient = OkHttpClient().newBuilder().connectTimeout(REQUEST_TIMEOUT.toLong(), TimeUnit.SECONDS)
                .readTimeout(REQUEST_TIMEOUT.toLong(), TimeUnit.SECONDS)
                .writeTimeout(REQUEST_TIMEOUT.toLong(), TimeUnit.SECONDS)

            val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY

            httpClient.addInterceptor(interceptor)

            httpClient.addInterceptor(object : Interceptor
            {
                override fun intercept(chain: Interceptor.Chain): Response
                {
                    var original = chain.request()
                    var requestBuilder = original.newBuilder()

                        .addHeader("Accept", "application/json").addHeader("Content-Type", "application/json");

                    // Adding Authorization token (API Key)
                    // Requests will be denied without API key
                    if (!TextUtils.isEmpty(PrefUtils.getApiKey(context)))
                    {
                        requestBuilder.addHeader("Authorization", PrefUtils.getApiKey(context));
                    }

                    var request = requestBuilder.build();

                    return chain.proceed(request)
                }


            })

            okHttpClient = httpClient.build()
        }
    }
}

PrefUtils Class

 class PrefUtils
{
    /**
     * Storing API Key in shared preferences to
     * add it in header part of every retrofit request
     */


    companion object
    {

        fun getSharedPreferences(context: Context): SharedPreferences
        {
            return context.getSharedPreferences("APP_PREF", Context.MODE_PRIVATE)
        }

        fun storeApiKey(context: Context, apiKey: String)
        {
            val editor = getSharedPreferences(context).edit()
            editor.putString("API_KEY", apiKey)
            editor.commit()
        }

        fun getApiKey(context: Context): String?
        {
            return getSharedPreferences(context).getString("API_KEY", null)
        }
    }
}
...