RXJava HTTP 500 Internal Server
up vote
0
down vote
favorite
I have converted RAVI TAMADA's RxJava tutorial from Java into Kotlin Android RxJava Networking with Retrofit, Gson – Notes App. On testing of the application just on the first network call I receive a HTTP 500 Internal Error (com.jakewharton.retrofit2.adapter.rxjava2.HttpException: HTTP 500 Internal Server Error). This is for the first network call registerUsers As far as conversion is concerned I have done everything by the book.
I have included the code base for the following classes
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
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
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)
}
}
}
java android-studio kotlin rx-java rx-java2
add a comment |
up vote
0
down vote
favorite
I have converted RAVI TAMADA's RxJava tutorial from Java into Kotlin Android RxJava Networking with Retrofit, Gson – Notes App. On testing of the application just on the first network call I receive a HTTP 500 Internal Error (com.jakewharton.retrofit2.adapter.rxjava2.HttpException: HTTP 500 Internal Server Error). This is for the first network call registerUsers As far as conversion is concerned I have done everything by the book.
I have included the code base for the following classes
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
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
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)
}
}
}
java android-studio kotlin rx-java rx-java2
1
all of the code is irrelevant. 500 means there is an error on the server. Check the server logs. We cannot help you
– Tim Castelijns
Nov 20 at 12:49
add a comment |
up vote
0
down vote
favorite
up vote
0
down vote
favorite
I have converted RAVI TAMADA's RxJava tutorial from Java into Kotlin Android RxJava Networking with Retrofit, Gson – Notes App. On testing of the application just on the first network call I receive a HTTP 500 Internal Error (com.jakewharton.retrofit2.adapter.rxjava2.HttpException: HTTP 500 Internal Server Error). This is for the first network call registerUsers As far as conversion is concerned I have done everything by the book.
I have included the code base for the following classes
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
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
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)
}
}
}
java android-studio kotlin rx-java rx-java2
I have converted RAVI TAMADA's RxJava tutorial from Java into Kotlin Android RxJava Networking with Retrofit, Gson – Notes App. On testing of the application just on the first network call I receive a HTTP 500 Internal Error (com.jakewharton.retrofit2.adapter.rxjava2.HttpException: HTTP 500 Internal Server Error). This is for the first network call registerUsers As far as conversion is concerned I have done everything by the book.
I have included the code base for the following classes
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
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
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)
}
}
}
java android-studio kotlin rx-java rx-java2
java android-studio kotlin rx-java rx-java2
edited Nov 20 at 12:45
asked Nov 20 at 12:26
George
428
428
1
all of the code is irrelevant. 500 means there is an error on the server. Check the server logs. We cannot help you
– Tim Castelijns
Nov 20 at 12:49
add a comment |
1
all of the code is irrelevant. 500 means there is an error on the server. Check the server logs. We cannot help you
– Tim Castelijns
Nov 20 at 12:49
1
1
all of the code is irrelevant. 500 means there is an error on the server. Check the server logs. We cannot help you
– Tim Castelijns
Nov 20 at 12:49
all of the code is irrelevant. 500 means there is an error on the server. Check the server logs. We cannot help you
– Tim Castelijns
Nov 20 at 12:49
add a comment |
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53392973%2frxjava-http-500-internal-server%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53392973%2frxjava-http-500-internal-server%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
all of the code is irrelevant. 500 means there is an error on the server. Check the server logs. We cannot help you
– Tim Castelijns
Nov 20 at 12:49