How to develop an android application separating data and view layer - android

I am new to Android development. have an android application in koltin wherein I have to make an http post request to get a list of data as response.
I have done that in activity class as follows.
MainActivity.kt
class MainActivity : AppCompatActivity(), {
private fun getAppList() {
var builder = AlertDialog.Builder(this#MainActivity)
builder.setTitle("App Response")
doAsync {
sslCertficate.disableSSLCertificateChecking()
var headers = HashMap<String, String>()
headers["Content-type"] = "application/json; charset=UTF-8"
val res = HTTPClient("https://sample-myapi-launcher.prod.com/list")
.setMethod("POST")
.setHeaders(headers)
.setBody(getRequestBody(userInfo.toString()))
.getResponse()
.response
uiThread {
builder.setMessage(res)
var dialog: AlertDialog = builder.create()
dialog.show()
}
Log.e("Response List", res)
}
}
private fun getRequestBody(userInfo: String): String {
//code for geting request body
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_navigator)
setSupportActionBar(toolbar)
//calling api request method
getAppList()
}
}
I could achieve my result through this, But I don't want to put all the work in the activity thread. Can someone guide on the correct approach to achieve this?
Or help me with some documentation.

This is the Android lifecycle-aware components codelab. It will do exatelly what you ask for. Here is the Architecture components part of the Android Jetpack and it is a set of Android libraries that help you structure your app in a way that is robust, testable, and maintainable.
Here is also the android-sunflower A gardening app illustrating Android development best practices with Android Jetpack.

to do networking I suggest you to use Retrofit2: Retrofit
Anyway to do networking operation in another thread you need to start a new AsyncTask from you activity and do the networking operations inside it.
In retrofit all this is much more simple!
(Sorry but I don't have Kotlin example for that below!)
Java Example without Retrofit:
( This was a my old project, so it isn't so good ^^)
/* Really Simple Class I made to do networking operations (so use Retrofit or make a better one (: (I suggest you, again, to use Retrofit!) */
public class DBConnection {
public String performPostCall(String requestURL, HashMap<String, String> postDataParams )
{
URL url;
String response = "";
try {
url = new URL(requestURL);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setReadTimeout(15000);
conn.setConnectTimeout(15000);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
conn.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());
conn.connect();
DataOutputStream dStream = new DataOutputStream(conn.getOutputStream());
dStream.writeBytes(getPostDataString(postDataParams));
dStream.flush();
dStream.close();
int responseCode = conn.getResponseCode();
if (responseCode == HttpsURLConnection.HTTP_OK) {
String line;
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while ((line=br.readLine()) != null) {
response += line;
}
} else {
response = "";
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
private String getPostDataString(HashMap<String, String> params) throws UnsupportedEncodingException
{
StringBuilder result = new StringBuilder();
boolean first = true;
for(Map.Entry<String, String> entry : params.entrySet()){
if (first) {
first = false;
} else {
result.append("&");
}
result.append(entry.getKey());
result.append("=");
result.append(entry.getValue());
}
return result.toString();
}
}
// AsyncTask to do Async Networking operations:
public class YourTask extends AsyncTask<String, Void, String> {
private String yourData...;
public YourTask(String token){
// Set Your Data
}
// "String..." is an array of arguments so you get the arguments usign: params[i]
#Override
protected String doInBackground(String... params)
{
DBConnection dbConn = new DBConnection();
String stampAnswer;
try{
Integer param1 = Integer.parseInt(params[0]);
Integer param2 = Integer.parseInt(params[1]);
answer = dbConn.netwokirngOperation([..]);
}catch(NumberFormatException nfe){
nfe.getStackTrace();
stampAnswer = "";
Log.e("YourTask", " NumberFormatException:");
}
return answer;
}
protected void onPostExecute(String result) {
Log.e("YourTask => ", " Result:" + result);
}
}
// To call the task do in your activity and do async networking operation without wait for result (this mean you need to save the data inside Realm DB [REALM][2] or using SQLite DB and then get them when the networking operations ended (you can use an Observable and set it when the networking operation end, send a broadcast message and set a receiver in you activity, or any other method):
new YourTask(<put_here_your_asynctask_constructor_args>).execute(params);
// To call the task and do async networking operation but wait to get the result returned by the "doInBackground" method of the AsyncTask:
new YourTask(<put_here_your_asynctask_constructor_args>).execute(params).get();
But is better if you use interface and callback to return the result from the AsyncTask when it ended, example:
/** in You Activity. Because in the Interface you use generic types (the 'T') you can specific the type of object returned by the interface inside the '<T>' if the interface WILL ALWAYS RETURN THE SAME OBJECT TYPE!
If it WILL RETURN DIFFERENT OBJECT TYPES you MUST don't specific the type inside the '<T>', but you have to cast the return inside a switch statement to know which object is returned (to do that you can add a 'int requestCase' to the interface so you know which case returned!) **/
public class YourActivity extends AppCompatActivity
implements IYourCallback<YourObjectTypesReturned>{
public interface IYourCallback<T>{
onNetOperationSuccess(List<T> answer)
onNetOperationError(Throwable t)
}
/** IMPLEMENTS HERE YOUR ACTIVITY BODY WITH THE INTERFACE METHODS ! **/
// Then call your AsyncTask where you want and pass it your context which implements the interface ( because you are in activity your context with the interface is "this"!
new YourTask(this).execute(params);
// Then inside your AsyncTask:
public class YourTask extends AsyncTask<String, Void, String> {
private IYourCallback mCallback;
public YourTask(Context context){
try{
mCallback = (IYourCallback) mCallback;
} catch(ClassCastException e){
onException(e); // Manage the exception and stop the operation
}
}
/** THEN IMPLEMENT YOU IN BACKGROUND... AND WHEN THE NETWORKING OPERATION IS FINISHED USE THE CALLBACK TO RETURN BACK YOUR RESULT, SO THE METHOD IN YOUR ACTIVITY WILL GET TRIGGERED AND YOU CAN CONTINUE TO DO YOUR OPERATIONS! So do: **/
if(success)
mCallback.onNetOperationSuccess(myListAnswer)
else
mCallback.onNetOperationError(error) // Throwable or exception
( Kotlin for the implementation of Retrofit! I started use Kotlin 5 days ago, so I don't know if this is the best use (: )
Example with Retrofit:
/* This is a RetrofitHelper which init the Retrofit instance and where you should put your networking methods. Then to do a networking operation you have to get this instance using (RetrofitHelper.getInstance().yourNetworkingOperation(...) ).
Anyway here there isn't the asynchronous part, you can get it in the link of my other comment below!
I don't have complete this class yet! */
class RetrofitHelper(baseUrl: String){
private val TAG = this.javaClass.name
// Timeouts
private val CONNECT_TIMEOUT = "CONNECT_TIMEOUT"
private val READ_TIMEOUT = "READ_TIMEOUT"
private val WRITE_TIMEOUT = "WRITE_TIMEOUT"
// Header Names
private val BASE_REQ_HEADER_NAME = "BLEDataBinder"
private val REQ_HEADER_NAME = "$BASE_REQ_HEADER_NAME.Request"
private val REQ_HEADER_VERSION_NAME = "$BASE_REQ_HEADER_NAME.VersionName"
private val REQ_HEADER_VERSION_CODE = "$BASE_REQ_HEADER_NAME.VersionCode"
private val REQ_HEADER_DEVICE_IMEI = "$BASE_REQ_HEADER_NAME.DeviceIMEI"
private val REQ_HEADER_DEVICE_UNIQUE_ID = "$BASE_REQ_HEADER_NAME.DeviceUniqueID"
private val REQ_HEADER_DEVICE_MODEL = "$BASE_REQ_HEADER_NAME.DeviceModel"
private val REQ_HEADER_ANDROID_RELEASE = "$BASE_REQ_HEADER_NAME.AndroidRelease"
// Header Values
private val REQ_HEADER_VALUE = "emax"
// Labels
private val LABEL_INIT = "Init RetrofitHelper"
private var mBaseUrl: String
private var mGson: Gson
private var mRetrofit: Retrofit
companion object {
#Volatile private var mInstance: RetrofitHelper? = null
fun getInstance() = mInstance
fun initInstance(baseUrl: String): RetrofitHelper =
mInstance ?: synchronized(this){
mInstance ?: newInstance(baseUrl).also { mInstance = it }
}
private fun newInstance(baseUrl: String) = RetrofitHelper(baseUrl)
}
init {
LogUtils.iLog(TAG, "START $LABEL_INIT")
val httpClient = OkHttpClient.Builder()
httpClient.addInterceptor( getInterceptor() )
httpClient.addInterceptor( getLoggingInterceptor() )
this.mBaseUrl = baseUrl
mGson = getGson()
mRetrofit = getRetrofit(httpClient.build())
LogUtils.iLog(TAG, "END $LABEL_INIT")
}
/* START Private Methods */
private fun getRetrofit(httpClient: OkHttpClient): Retrofit{
return Retrofit.Builder()
.baseUrl(mBaseUrl)
.addConverterFactory(GsonConverterFactory.create(mGson))
.client(httpClient)
.build()
}
private fun getGson(): Gson{
return GsonBuilder()
.setDateFormat(Constants.DATETIME_FORMAT_DB)
.registerTypeAdapter(Boolean::class.javaObjectType, BooleanDeserializer())
.create()
}
private fun getLoggingInterceptor() =
HttpLoggingInterceptor {
getLoggingInterceptorLogger()
}.also { it.level = HttpLoggingInterceptor.Level.BODY }
private fun getLoggingInterceptorLogger() =
HttpLoggingInterceptor.Logger {
message -> HyperLog.v(TAG, message)
}
private fun getInterceptor(): Interceptor =
Interceptor {
buildInterceptorResponse(it)
}
private fun buildInterceptorResponse(chain: Interceptor.Chain): Response {
val builder: Request.Builder = chain.request().newBuilder().addHeader(REQ_HEADER_NAME, REQ_HEADER_VALUE)
setRequestHeaderVersionName(builder)
setRequestHeaderVersionCode(builder)
setRequestHeaderDeviceIMEI(builder)
setRequestHeaderDeviceUniqueID(builder)
setRequestHeaderDeviceModel(builder)
setRequestHeaderAndroidRelease(builder)
/* This part let you set custom timeout for different api call inside the "RetrofitAPI" interface using that labels: (example inside the RetrofitAPI interface)
public interface RetrofitAPI {
#Headers({RetrofitHelper.CONNECT_TIMEOUT + ":100000", RetrofitHelper.READ_TIMEOUT + ":100000"})
#FormUrlEncoded
#POST
Call<JsonObject> doBaseJsonRequest(#Url String url, #Field("params") String params);
}
*/
var connectTimeout = chain.connectTimeoutMillis()
var readTimeout = chain.readTimeoutMillis()
var writeTimeout = chain.writeTimeoutMillis()
val request = chain.request()
if(!TextUtils.isEmpty(request.header(CONNECT_TIMEOUT))){
connectTimeout = request.header(CONNECT_TIMEOUT)!!.toInt()
}
if(!TextUtils.isEmpty(request.header(READ_TIMEOUT))){
readTimeout = request.header(READ_TIMEOUT)!!.toInt()
}
if(!TextUtils.isEmpty(request.header(WRITE_TIMEOUT))){
writeTimeout = request.header(WRITE_TIMEOUT)!!.toInt()
}
builder.removeHeader(CONNECT_TIMEOUT)
builder.removeHeader(READ_TIMEOUT)
builder.removeHeader(WRITE_TIMEOUT)
return chain
.withConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.withReadTimeout(readTimeout, TimeUnit.MILLISECONDS)
.withWriteTimeout(writeTimeout, TimeUnit.MILLISECONDS)
.proceed(builder.build())
}
/*private fun setRequestHeaders(builder: Request.Builder): Request.Builder{
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mVersionName)){
builder.addHeader(REQ_HEADER_VERSION_NAME, AppEnvironment.getInstance()!!.mVersionName!!)
}
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mVersionCode.toString())){
builder.addHeader(REQ_HEADER_VERSION_CODE, AppEnvironment.getInstance()!!.mVersionName!!)
}
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mDeviceIMEI)){
builder.addHeader(REQ_HEADER_DEVICE_IMEI, AppEnvironment.getInstance()!!.mVersionName!!)
}
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mDeviceUniqueID)){
builder.addHeader(REQ_HEADER_DEVICE_UNIQUE_ID, AppEnvironment.getInstance()!!.mVersionName!!)
}
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mDeviceModel)){
builder.addHeader(REQ_HEADER_DEVICE_MODEL, AppEnvironment.getInstance()!!.mVersionName!!)
}
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mAndroidRelease)){
builder.addHeader(REQ_HEADER_ANDROID_RELEASE, AppEnvironment.getInstance()!!.mVersionName!!)
}
return builder
}*/
private fun setRequestHeaderVersionName(builder: Request.Builder){
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mVersionName)){
builder.addHeader(REQ_HEADER_VERSION_NAME, AppEnvironment.getInstance()!!.mVersionName!!)
}
}
private fun setRequestHeaderVersionCode(builder: Request.Builder){
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mVersionCode.toString())){
builder.addHeader(REQ_HEADER_VERSION_CODE, AppEnvironment.getInstance()!!.mVersionName!!)
}
}
private fun setRequestHeaderDeviceIMEI(builder: Request.Builder){
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mDeviceIMEI)){
builder.addHeader(REQ_HEADER_DEVICE_IMEI, AppEnvironment.getInstance()!!.mVersionName!!)
}
}
private fun setRequestHeaderDeviceUniqueID(builder: Request.Builder){
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mDeviceUniqueID)){
builder.addHeader(REQ_HEADER_DEVICE_UNIQUE_ID, AppEnvironment.getInstance()!!.mVersionName!!)
}
}
private fun setRequestHeaderDeviceModel(builder: Request.Builder){
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mDeviceModel)){
builder.addHeader(REQ_HEADER_DEVICE_MODEL, AppEnvironment.getInstance()!!.mVersionName!!)
}
}
private fun setRequestHeaderAndroidRelease(builder: Request.Builder){
if(!TextUtils.isEmpty(AppEnvironment.getInstance()!!.mAndroidRelease)){
builder.addHeader(REQ_HEADER_ANDROID_RELEASE, AppEnvironment.getInstance()!!.mVersionName!!)
}
}
/* END Private Methods */
}
LINK TO MY COMMENT FOR ASYNC RETROFIT USAGE WITH GENERIC TYPES METHODS AND CALLBACK INTERFACES:
RETROFIT ASYNC WITH GENERIC TYPES
(This is in java but you can easily translate it in Kotlin! Also I suggest you to learn Java too because Kotlin is a scripting language built up Java so it translates the code in Java operations whose sometimes are really bigger (and sometimes slower) than if you wrote the code in Java! So, now I am learning Kotlin after learned Java for android applications, Kotlin is a really good, smart, beautiful and fast programming language for apps and I will use it to do fast and smart scripts inside my applications or for simple applications, but I will use Java too because with it you can generate faster code.
Hope this is helpful,
Bye and Good Coding! (:

Related

what is the equivalant of rxJava onNext and onError in coroutines

Hi I have some usecases which are written in Java which uses rxJava. I have converted them to kotlin files and instead of rxJava I have made them into couroutines suspend functions.
In my rxJava code I am making an api call from the usecase and it returns the result but at the same time onNext it does something and onError it does something.
How can I do the same thing in coroutines
here is my rxjava code
#PerApp
public class StartFuellingUseCase {
#Inject
App app;
#Inject
CurrentOrderStorage orderStorage;
#Inject
FuelOrderRepository repository;
#Inject
StartFuellingUseCase() {
// empty constructor for injection usage
}
public Observable<GenericResponse> execute(Equipment equipment) {
if (orderStorage.getFuelOrder() == null) return null;
DateTime startTime = new DateTime();
TimestampedAction action = new TimestampedAction(
app.getSession().getUser().getId(), null, startTime
);
return repository.startFuelling(orderStorage.getFuelOrder().getId(), action)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(response -> onSuccess(startTime, equipment))
.doOnError(this::onError);
}
private void onSuccess(DateTime startTime, Equipment equipment) {
if (orderStorage.getFuelOrder() == null) return;
orderStorage.getFuelOrder().setStatus(FuelOrderData.STATUS_FUELLING);
equipment.getTimes().setStart(startTime);
app.saveState();
}
private void onError(Throwable e) {
Timber.e(e, "Error calling started fuelling! %s", e.getMessage());
}
}
I have re written the code in Kotlin using coroutines usecases
#PerApp
class StartFuellingUseCaseCoroutine #Inject constructor(
private val currentOrderStorage: CurrentOrderStorage,
private val fuelOrderRepository: FuelOrderRepository,
private val app: App
): UseCaseCoroutine<GenericResponse, StartFuellingUseCaseCoroutine.Params>() {
override suspend fun run(params: Params): GenericResponse {
val startTime = DateTime()
val action = TimestampedAction(
app.session.user.id, null, startTime
)
return fuelOrderRepository.startFuelling(
currentOrderStorage.fuelOrder!!.id,
action
)
//SHOULD RETURN THE VALUE FROM THE fuelOrderRepository.startFuelling
//AND ALSO
//ON NEXT
//CALL onSuccess PASSING startTime and equipment
//ON ERROR
//CALL onError
}
private fun onSuccess(startTime: DateTime, equipment: Equipment) {
if (currentOrderStorage.getFuelOrder() == null) return
currentOrderStorage.getFuelOrder()!!.setStatus(FuelOrderData.STATUS_FUELLING)
equipment.times.start = startTime
app.saveState()
}
private fun onError(errorMessage: String) {
Timber.e(errorMessage, "Error calling started fuelling! %s", errorMessage)
}
data class Params(val equipment: Equipment)
}
Can you please suggest how can i call onSuccess and onError similar to how we have in rxjava onnext and onError.
could you please suggest how to fix this
thanks
R
You can using Kotlin Flow like converted example below:
RxJava
private fun observable(
value: Int = 1
): Observable<Int> {
return Observable.create { emitter ->
emitter.onNext(value)
emitter.onError(RuntimeException())
}
}
Flow:
private fun myFlow(
value: Int = 1
): Flow<Int> {
return flow {
emit(value)
throw RuntimeException()
}
}
For more detail : https://developer.android.com/kotlin/flow
convert startFuelling to flow using flowOf, you can do below
return flowOf(repository
.startFuelling(orderStorage.getFuelOrder().getId(), action))
.onEach{response -> onSuccess(startTime, equipment)}
.catch{e -> onError(e) }
.flowOn(Dispatchers.IO) //this will make above statements to execute on IO
if you want to collect it on main thread, you can use launchIn
.onEach{ }
.launchIn(mainScope)//could be lifeCycleScope/viewModelScope
//or
CoroutineScope(Dispatchers.Main).launch{
flow.collect{}
}

Kotlin and Retrofit : How to Handle HTTP 400 responses?

I am using Retrofit (2.6) on Android to implement a service which connects to a web server, and which requests that the server undertake some work. The relevant code can be summarized thus:
interface MyService {
#GET(START_WORK)
suspend fun startWork(#Query("uuid") uuid: String,
#Query("mode") mode: Int):
MyStartWorkResponse
}
// Do some other things, include get a reference to a properly configured
// instance of Retrofit.
// Instantiate service
var service: MyService = retrofit.create(MyService::class.java)
I can call service.startWork() with no problem and obtain valid results. However, in some conditions, the web server will return a 400 error code, with a response body which includes specific error information. The request is not malformed, however; it's just that there is another problem which should be brought to the user's attention. The trouble is, I can't tell what the problem is, because I don't get a response; instead, my call throws an exception because of the 400 error code.
I don't understand how to modify my code so that I can catch and handle 400 error responses, and get the information I need from the body of the response. Is this a job for a network interceptor on my okhttp client? Can anyone shed some light?
Use this code (KOTLIN)
class ApiClient {
companion object {
private val BASE_URL = "YOUR_URL_SERVER"
private var retrofit: Retrofit? = null
private val okHttpClientvalor = OkHttpClient.Builder()
.connectTimeout(90, TimeUnit.SECONDS)
.writeTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS)
.build()
fun apiClient(): Retrofit {
if (retrofit == null) {
retrofit = Retrofit.Builder().baseUrl(BASE_URL)
.client(okHttpClientvalor)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit!!
}
}
}
object ErrorUtils {
fun parseError(response: Response<*>): ErrorResponce? {
val conversorDeErro = ApiClient.apiClient()
.responseBodyConverter<ErrorResponce>(ErrorResponce::class.java, arrayOfNulls(0))
var errorResponce: ErrorResponce? = null
try {
if (response.errorBody() != null) {
errorResponce = conversorDeErro.convert(response.errorBody()!!)
}
} catch (e: IOException) {
return ErrorResponce()
} finally {
return errorResponce
}
}
}
class ErrorResponce {
/* This name "error" must match the message key returned by the server.
Example: {"error": "Bad Request ....."} */
#SerializedName("error")
#Expose
var error: String? = null
}
if (response.isSuccessful) {
return MyResponse(response.body() // transform
?: // some empty object)
} else {
val errorResponce = ErrorUtils.parseError(response)
errorResponce!!.error?.let { message ->
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
}
Retrofit defines successful response as such:
public boolean isSuccessful() {
return code >= 200 && code < 300; }
which means you should be able to do something like this
class ServiceImpl(private val myService: MyService) {
suspend fun startWork(//query): MyResponse =
myService.startWork(query).await().let {
if (response.isSuccessful) {
return MyResponse(response.body()//transform
?: //some empty object)
} else {
throw HttpException(response)//or handle - whatever
}
}
}

RxKotlin with Socket.io in Android

What I trying to do is listen to socket data and convert into an observable string that my UI can Subscribe this event and do Change on UI
So far I created a class SocketConnection maintain in dagger connection happen properly and received data and able to do with interface correctly, but want to apply with rxkotlin.
Using Socket.io,kotlin
SocketConnection class
class SocketConnection : SocketStreamListener {
private var socket: Socket? = null
var responseSocket :ResponseHandler?= null
companion object {
var instance = SocketConnection()
}
override fun createSocket(socketQuery: SocketQuery): Socket? {
try {
val okHttpClient = UnsafeOkHttpClient.getUnsafeOkHttpClient()
IO.setDefaultOkHttpWebSocketFactory(okHttpClient)
IO.setDefaultOkHttpCallFactory(okHttpClient)
val opts = IO.Options()
opts.reconnection = false
opts.callFactory = okHttpClient
opts.webSocketFactory = okHttpClient
opts.query = "userID=" + socketQuery.userID + "&token=" + socketQuery.token
socket = IO.socket(CommonContents.BASE_API_LAYER, opts)
L.d("Socket object created")
} catch (e: URISyntaxException) {
L.e("Error creating socket", e)
}
return socket
}
override fun createSocketListener(socket: Socket) {
L.d("inside the socket Listner")
socket.connect()?.on(Socket.EVENT_CONNECT, {
L.d("connected")
listenSocketEvents()
//socketDataListener()
createMessageListener()
})?.on(Socket.EVENT_DISCONNECT,
{
L.d("disconnected")
return#on
})
}
/**
* function used to listen a socket chanel data
*/
private fun listenSocketEvents() {
/* socket?.on("1502", { args ->
// This Will Work
L.d("Socket market depth event successfully")
val socketData = args[0] as String
L.d(socketData)
// instance.data = Observable.just(socketData)
//data!!.doOnNext({ socketData })
*//*
data = args[0] as String
for (i in 0 until arr.size) {
arr[i].socketStreamingData(data)
}*//*
})*/
}
// This Will Not Work
fun socketDataListener(): Observable<String>{
return Observable.create({
subscibe ->
// L.d("Socket market depth event successfully")
socket?.on("1502", { args ->
L.d("Socket market depth event successfully")
val socketData = args[0] as String
subscibe.onNext(socketData)
})
})
}
}
Repository
fun getSocketData(): Observable<String> {
// L.e("" + SocketConnection.instance.socketDataListener())
return SocketConnection.instance.createMessageListener()
}
ViewModel
fun getSocketData(): Observable<String>{
return groupRepository.getSocketData()
}
OnFragement (UI)
private fun getSocketUpdate(){
subscribe(watchlistViewModel.getSocketData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
L.d("SocketData : " + it.count())
}, {
L.e("Error")
}))
}
In this UI using disposable subscribe method into base class.
Please let me know what i doing wrong thanx in advance
Instead of creating an Observable every time a message is sent, I suggest using a Subject for that, since it has a similar "nature" as the Socket connection.
val subject = PublishSubject.create<String>()
...
fun listenSocketEvents() {
socket?.on("1502") { args ->
val socketData = args[0] as String
subject.onNext(socketData)
}
}
fun observable(): Observable<String>{
return subject
}
You can then listen to the changes on the subject via (repository layer etc not included, you'd have to do that yourself)
private fun getSocketUpdate() {
disposable = socketConnection.observable()
.subscribeOn(Schedulers.io())
.observeOn(...)
.subscribe({...}, {...})
}
As a side note, your singleton instance is not how you'd do that in kotlin.
Instead of having an instance field in a companion object, you should make the declare the class as object SocketConnection.
This will automatically give you all singleton features. (I do not know whether it is smart to use a singleton with socket.io, but I assume that you know what you're doing :-) )

OKHTTP 3 Tracking Multipart upload progress

How can I track progress of upload in OkHttp 3
I can find answers for v2 but not v3, like this
A sample Multipart request from OkHttp recipes
private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png",
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
You can decorate your OkHttp request body to count the number of bytes written when writing it; in order to accomplish this task, wrap your MultiPart RequestBody in this RequestBody with an instance of Listener and Voila!
public class ProgressRequestBody extends RequestBody {
protected RequestBody mDelegate;
protected Listener mListener;
protected CountingSink mCountingSink;
public ProgressRequestBody(RequestBody delegate, Listener listener) {
mDelegate = delegate;
mListener = listener;
}
#Override
public MediaType contentType() {
return mDelegate.contentType();
}
#Override
public long contentLength() {
try {
return mDelegate.contentLength();
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
#Override
public void writeTo(BufferedSink sink) throws IOException {
mCountingSink = new CountingSink(sink);
BufferedSink bufferedSink = Okio.buffer(mCountingSink);
mDelegate.writeTo(bufferedSink);
bufferedSink.flush();
}
protected final class CountingSink extends ForwardingSink {
private long bytesWritten = 0;
public CountingSink(Sink delegate) {
super(delegate);
}
#Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
mListener.onProgress((int) (100F * bytesWritten / contentLength()));
}
}
public interface Listener {
void onProgress(int progress);
}
}
Check this link for more.
I was unable to get any of the answers to work for me. The issue was that the progress would run to 100% before the image was uploaded hinting that some buffer was getting filled prior to the data being sent over the wire. After some research, I found this was indeed the case and that buffer was the Socket send buffer. Providing a SocketFactory to the OkHttpClient finally worked. My Kotlin code is as follows...
First, As others, I have a CountingRequestBody which is used to wrap the MultipartBody.
class CountingRequestBody(var delegate: RequestBody, private var listener: (max: Long, value: Long) -> Unit): RequestBody() {
override fun contentType(): MediaType? {
return delegate.contentType()
}
override fun contentLength(): Long {
try {
return delegate.contentLength()
} catch (e: IOException) {
e.printStackTrace()
}
return -1
}
override fun writeTo(sink: BufferedSink) {
val countingSink = CountingSink(sink)
val bufferedSink = Okio.buffer(countingSink)
delegate.writeTo(bufferedSink)
bufferedSink.flush()
}
inner class CountingSink(delegate: Sink): ForwardingSink(delegate) {
private var bytesWritten: Long = 0
override fun write(source: Buffer, byteCount: Long) {
super.write(source, byteCount)
bytesWritten += byteCount
listener(contentLength(), bytesWritten)
}
}
}
I'm using this in Retrofit2. A general usage would be something like this:
val builder = MultipartBody.Builder()
// Add stuff to the MultipartBody via the Builder
val body = CountingRequestBody(builder.build()) { max, value ->
// Progress your progress, or send it somewhere else.
}
At this point, I was getting progress, but I would see 100% and then a long wait while the data was uploading. The key was that the socket was, by default in my setup, configured to buffer 3145728 bytes of send data. Well, my images were just under that and the progress was showing the progress of filling that socket send buffer. To mitigate that, create a SocketFactory for the OkHttpClient.
class ProgressFriendlySocketFactory(private val sendBufferSize: Int = DEFAULT_BUFFER_SIZE) : SocketFactory() {
override fun createSocket(): Socket {
return setSendBufferSize(Socket())
}
override fun createSocket(host: String, port: Int): Socket {
return setSendBufferSize(Socket(host, port))
}
override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket {
return setSendBufferSize(Socket(host, port, localHost, localPort))
}
override fun createSocket(host: InetAddress, port: Int): Socket {
return setSendBufferSize(Socket(host, port))
}
override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket {
return setSendBufferSize(Socket(address, port, localAddress, localPort))
}
private fun setSendBufferSize(socket: Socket): Socket {
socket.sendBufferSize = sendBufferSize
return socket
}
companion object {
const val DEFAULT_BUFFER_SIZE = 2048
}
}
And during config, set it.
val clientBuilder = OkHttpClient.Builder()
.socketFactory(ProgressFriendlySocketFactory())
As others have mentioned, Logging the request body may effect this and cause the data to be read more than once. Either don't log the body, or what I do is turn it off for CountingRequestBody. To do so, I wrote my own HttpLoggingInterceptor and it solves this and other issues (like logging MultipartBody). But that's beyond the scope fo this question.
if(requestBody is CountingRequestBody) {
// don't log the body in production
}
The other issues was with MockWebServer. I have a flavor that uses MockWebServer and json files so my app can run without a network so I can test without that burden. For this code to work, the Dispatcher needs to read the body data. I created this Dispatcher to do just that. Then it forwards the dispatch to another Dispatcher, such as the default QueueDispatcher.
class BodyReadingDispatcher(val child: Dispatcher): Dispatcher() {
override fun dispatch(request: RecordedRequest?): MockResponse {
val body = request?.body
if(body != null) {
val sink = ByteArray(1024)
while(body.read(sink) >= 0) {
Thread.sleep(50) // change this time to work for you
}
}
val response = child.dispatch(request)
return response
}
}
You can use this in the MockWebServer as:
var server = MockWebServer()
server.setDispatcher(BodyReadingDispatcher(QueueDispatcher()))
This is all working code in my project. I did pull it out of illustration purposes. If it does not work for you out of the box, I apologize.
According to Sourabh's answer, I want tell that field of CountingSink
private long bytesWritten = 0;
must be moved into ProgressRequestBody class

Android with Kotlin - How to use HttpUrlConnection

I´m trying to get data from a url inside an AsyncTask but I get an error when creating a new instance of HttpUrlConnection.
Something like this on Java
URL url = new URL("http://www.android.com/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
readStream(in);
finally {
urlConnection.disconnect();
}
But I keep getting the error shown below.
class GetWeatherTask : AsyncTast<Void, Void, Void>() {
override fun doInBackground(vararg params: Void?): Void? {
val httpClient = HttpURLConnection();
return null
}
override fun onPreExecute() {
super.onPreExecute()
}
override fun onPostExecute(result: Void?) {
super.onPostExecute(result)
}
}
Cannot access '': it is 'protected/protected and package/' in 'HttpURLConnection' Cannot create an instance of an abstract class
Am I missing something? I tryied to create a class object extending HttpUrlConnection and try to implement the init method but I couldn't
Thanks in advance.
Here is a simplification of the question and answer.
Why does this fail?
val connection = HttpURLConnection()
val data = connection.inputStream.bufferedReader().readText()
// ... do something with "data"
with error:
Kotlin: Cannot access '': it is 'protected/protected and package/' in 'HttpURLConnection'
This fails because you are constructing a class that is not intended to directly be constructed. It is meant to be created by a factory, which is in the URL class openConnection() method. This is also not a direct port of the sample Java code in the original question.
The most idiomatic way in Kotlin to open this connection and read the contents as a string would be:
val connection = URL("http://www.android.com/").openConnection() as HttpURLConnection
val data = connection.inputStream.bufferedReader().readText()
This form will auto close everything when done reading the text or on an exception. If you want to do custom reading:
val connection = URL("http://www.android.com/").openConnection() as HttpURLConnection
connection.inputStream.bufferedReader().use { reader ->
// ... do something with the reader
}
NOTE: the use() extension function will open and close the reader and handle closing on errors automatically.
About the disconnect() method
The docs for disconnect say:
Each HttpURLConnection instance is used to make a single request
but the underlying network connection to the HTTP server may be
transparently shared by other instances. Calling the close() methods
on the InputStream or OutputStream of an HttpURLConnection
after a request may free network resources associated with this
instance but has no effect on any shared persistent connection.
Calling the disconnect() method may close the underlying socket
if a persistent connection is otherwise idle at that time.
So you decide if you want to call it or not. Here is a version of the code that calls disconnect:
val connection = URL("http://www.android.com/").openConnection() as HttpURLConnection
try {
val data = connection.inputStream.bufferedReader().use { it.readText() }
// ... do something with "data"
} finally {
connection.disconnect()
}
The simplest way to do a get post request using HTTPUrlConnection is to create a common helper class that can be called from anywhere in the app to call the GET and POST request methods, without writing the same code again and again.
Below is the helper object (Singleton) class that you can use for the network call for GET and POST requests.
package com.dewari.ajay.androidnetworkcommunication.network
import org.json.JSONObject
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.IOException
import java.io.InputStreamReader
import java.io.OutputStream
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import javax.net.ssl.HttpsURLConnection
object RequestHandler {
const val GET : String = "GET"
const val POST : String = "POST"
#Throws(IOException::class)
fun requestPOST(r_url: String?, postDataParams: JSONObject): String? {
val url = URL(r_url)
val conn: HttpURLConnection = url.openConnection() as HttpURLConnection
conn.readTimeout = 3000
conn.connectTimeout = 3000
conn.requestMethod = POST
conn.doInput = true
conn.doOutput = true
val os: OutputStream = conn.outputStream
val writer = BufferedWriter(OutputStreamWriter(os, "UTF-8"))
writer.write(encodeParams(postDataParams))
writer.flush()
writer.close()
os.close()
val responseCode: Int = conn.responseCode // To Check for 200
if (responseCode == HttpsURLConnection.HTTP_OK) {
val `in` = BufferedReader(InputStreamReader(conn.inputStream))
val sb = StringBuffer("")
var line: String? = ""
while (`in`.readLine().also { line = it } != null) {
sb.append(line)
break
}
`in`.close()
return sb.toString()
}
return null
}
#Throws(IOException::class)
fun requestGET(url: String?): String? {
val obj = URL(url)
val con = obj.openConnection() as HttpURLConnection
con.requestMethod = GET
val responseCode = con.responseCode
println("Response Code :: $responseCode")
return if (responseCode == HttpURLConnection.HTTP_OK) { // connection ok
val `in` =
BufferedReader(InputStreamReader(con.inputStream))
var inputLine: String?
val response = StringBuffer()
while (`in`.readLine().also { inputLine = it } != null) {
response.append(inputLine)
}
`in`.close()
response.toString()
} else {
""
}
}
#Throws(IOException::class)
private fun encodeParams(params: JSONObject): String? {
val result = StringBuilder()
var first = true
val itr = params.keys()
while (itr.hasNext()) {
val key = itr.next()
val value = params[key]
if (first) first = false else result.append("&")
result.append(URLEncoder.encode(key, "UTF-8"))
result.append("=")
result.append(URLEncoder.encode(value.toString(), "UTF-8"))
}
return result.toString()
}
}
Using the above object class you can do your GET and POST requests as shown below:
//As this is network call it should be done in a separate thread
Thread(Runnable {
RequestHandler.requestGET(url)
RequestHandler.requestPOST(url, postJSONObject)
}).start()
Instead of using thread you can also use AsyncTask as followed:
class NetworkAsyncCall(private val context: Context, private val url: String, private val requestType:
String, private val postJSONObject: JSONObject = JSONObject()
) : AsyncTask<String?, String?, String?>() {
override fun doInBackground(vararg p0: String?): String? {
return when (requestType) {
RequestHandler.GET -> RequestHandler.requestGET(url)
RequestHandler.GET -> RequestHandler.requestPOST(url, postJSONObject)
else -> ""
}
}
override fun onPostExecute(s: String?) {
if (s != null) {
Toast.makeText(context, s, Toast.LENGTH_LONG).show()
}
}
}
You can create the asyncTask as a inner class of Activity or a seperate indipendent class.
Now to call the newtwork call via the AsyncTask NetworkAsyncCall in your onCreate() or any function from which you want call the api you can write:
NOTE: The mentioned url will not work so, you have to replace it with your own.
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)
// Change the url with your own GET URL request
val urlGET = "http://my-json-feed"
//GET Request
NetworkAsyncCall(this#MainActivity, urlGET, RequestHandler.GET).execute();
// POST Request
// doPost()
}
For POST request you can call:
private fun doPost() {
// Change the url with your own POST URL request
val urlPOST = "http://my-json-feed"
val postDataParams = JSONObject()
postDataParams.put("name", "Ajay")
postDataParams.put("email", "aj****ri#gmail.com")
postDataParams.put("phone", "+91 78******25")
NetworkAsyncCall(this#MainActivity, urlPOST, RequestHandler.POST, postDataParams).execute()
}
you can check the complete code in github here.
For a good explanation you can check this link.
the advantage of using the NetworkAsyncCall as seperate indipendent class is you don't have to write the AsyncTask code again, just call the same AsyncTask NetworkAsyncCall with a new object from different activitys/functions, however with this you have to implement a listener interface that you will require for the callback on onPostExecute() after getting the response from the api and to return back the response to the activity you have to perform the callback using that interface.
You can adaptaion your code; Don't forget user Runnable thread.
Thread(Runnable {
try {
val url = URL("www.android.com")
val con = url.openConnection() as HttpURLConnection
val datas = con.inputStream.bufferedReader().readText()
val json = JSONObject(datas)
val blockList = json.getJSONObject("blockList")
val warning = json.get("warnMessage").toString()
val keys = blockList.keys()
var permission = HashMap<String, Array<String?>>()
while (keys.hasNext()) {
val key = keys.next()
val kods = blockList.getJSONArray(key)
val permissonArray = arrayOfNulls<String>(kods.length())
for (i in permissonArray.indices) {
permissonArray[i] = kods.getString(i)
}
permission[key] = permissonArray;
}
} catch (ex: Exception) {
Log.d("Exception", ex.toString())
}
}).start()

Categories

Resources