I'm new to retrofit. I've searched but didn't found a simple answer. I want to know how can I show progress of download in Notification bar or at least show a progress dialog which specifies the percent of process and size of downloading file.
Here is my code:
public interface ServerAPI {
#GET
Call<ResponseBody> downlload(#Url String fileUrl);
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl("http://192.168.43.135/retro/")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
public void download(){
ServerAPI api = ServerAPI.retrofit.create(ServerAPI.class);
api.downlload("https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png").enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
File path = Environment.getExternalStorageDirectory();
File file = new File(path, "file_name.jpg");
FileOutputStream fileOutputStream = new FileOutputStream(file);
IOUtils.write(response.body().bytes(), fileOutputStream);
}
catch (Exception ex){
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
please guide me if you can.
thanks
You need to create a specific OkHttp client which will intercept the network requests and send updates. This client should only be used for downloads.
First you are going to need an interface, like this one:
public interface OnAttachmentDownloadListener {
void onAttachmentDownloadedSuccess();
void onAttachmentDownloadedError();
void onAttachmentDownloadedFinished();
void onAttachmentDownloadUpdate(int percent);
}
Your download call should return a ResponseBody, which we will extend from to be able to get the download progress.
private static class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final OnAttachmentDownloadListener progressListener;
private BufferedSource bufferedSource;
public ProgressResponseBody(ResponseBody responseBody, OnAttachmentDownloadListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
#Override public MediaType contentType() {
return responseBody.contentType();
}
#Override public long contentLength() {
return responseBody.contentLength();
}
#Override public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
#Override public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
float percent = bytesRead == -1 ? 100f : (((float)totalBytesRead / (float) responseBody.contentLength()) * 100);
if(progressListener != null)
progressListener.onAttachmentDownloadUpdate((int)percent);
return bytesRead;
}
};
}
}
Then you will need to create your OkHttpClient like this
public OkHttpClient.Builder getOkHttpDownloadClientBuilder(OnAttachmentDownloadListener progressListener) {
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
// You might want to increase the timeout
httpClientBuilder.connectTimeout(20, TimeUnit.SECONDS);
httpClientBuilder.writeTimeout(0, TimeUnit.SECONDS);
httpClientBuilder.readTimeout(5, TimeUnit.MINUTES);
httpClientBuilder.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
if(progressListener == null) return chain.proceed(chain.request());
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), progressListener))
.build();
}
});
return httpClientBuilder;
}
Finally you only have to create your Retrofit client a different way, by passing your new OkHttp client. Based on your code, you can use something like this:
public Retrofit getDownloadRetrofit(OnAttachmentDownloadListener listener) {
return new Retrofit.Builder()
.baseUrl("http://192.168.43.135/retro/")
.addConverterFactory(GsonConverterFactory.create())
.client(getOkHttpDownloadClientBuilder(listener).build())
.build();
}
Your listener will handle the creation of your notification or whatever else you want.
Here is another Kotlin solution using Flow
interface MyService {
#Streaming // allows streaming data directly to fs without holding all contents in ram
#GET
suspend fun getUrl(#Url url: String): ResponseBody
}
sealed class Download {
data class Progress(val percent: Int) : Download()
data class Finished(val file: File) : Download()
}
fun ResponseBody.downloadToFileWithProgress(directory: File, filename: String): Flow<Download> =
flow {
emit(Download.Progress(0))
// flag to delete file if download errors or is cancelled
var deleteFile = true
val file = File(directory, "${filename}.${contentType()?.subtype}")
try {
byteStream().use { inputStream ->
file.outputStream().use { outputStream ->
val totalBytes = contentLength()
val data = ByteArray(8_192)
var progressBytes = 0L
while (true) {
val bytes = inputStream.read(data)
if (bytes == -1) {
break
}
outputStream.write(data, 0, bytes)
progressBytes += bytes
emit(Download.Progress(percent = ((progressBytes * 100) / totalBytes).toInt()))
}
when {
progressBytes < totalBytes ->
throw Exception("missing bytes")
progressBytes > totalBytes ->
throw Exception("too many bytes")
else ->
deleteFile = false
}
}
}
emit(Download.Finished(file))
} finally {
// check if download was successful
if (deleteFile) {
file.delete()
}
}
}
.flowOn(Dispatchers.IO)
.distinctUntilChanged()
suspend fun Context.usage() {
coroutineScope {
myService.getUrl("https://www.google.com")
.downloadToFileWithProgress(
externalCacheDir!!,
"my_file",
)
.collect { download ->
when (download) {
is Download.Progress -> {
// update ui with progress
}
is Download.Finished -> {
// update ui with file
}
}
}
}
}
Here is my variant with Kotlin's coroutines
Specify API interface. We need #Streaming annotation to say Retrofit that we want to handle the response body manually. Otherwise, retrofit will try to write your file straight into RAM
interface Api {
#Streaming
#GET("get-zip-ulr/{id}")
fun getZip(#Path("id") id: Int): Call<ResponseBody>
}
Create DataSource which will control downloading process
class FilesDataSource(private val parentFolder: File, private val api: Api) {
suspend fun downloadZip(id: Int, processCallback: (Long, Long) -> Unit): File {
val response = api.getZip(id).awaitResponse()// returns the response, but it's content will be later
val body = response.body()
if (response.isSuccessful && body != null) {
val file = File(parentFolder, "$id")
body.byteStream().use { inputStream ->
FileOutputStream(file).use { outputStream ->
val data = ByteArray(8192)
var read: Int
var progress = 0L
val fileSize = body.contentLength()
while (inputStream.read(data).also { read = it } != -1) {
outputStream.write(data, 0, read)
progress += read
publishProgress(processCallback, progress, fileSize)
}
publishProgress(processCallback, fileSize, fileSize)
}
}
return file
} else {
throw HttpException(response)
}
}
private suspend fun publishProgress(
callback: (Long, Long) -> Unit,
progress: Long, //bytes
fileSize: Long //bytes
) {
withContext(Dispatchers.Main) { // invoke callback in UI thtread
callback(progress, fileSize)
}
}
}
Now you can execute downloadZip() method in your ViewModel or Presenter and give it a callback which will be linked to some ProgerssBar. After download completion, you will receive the downloaded file.
None of provided answers works correctly, here is a working solution which combine both approach correctly.
First create new retrofit object for download, this retrofit object shouldn't contain any log interceptor because this will cause java.lang.IllegalStateException: closed
fun interface ResponseBodyListener {
fun update(responseBody: ResponseBody)
}
fun getDownloaderRetrofit(listener: ResponseBodyListener): Retrofit = Retrofit.Builder()
.baseUrl("https://example.com")// <-- this is just a placeholder, we will not use it either way in the request.
.client(initHttpDownloadListenerClient(listener))
.build()
private fun initHttpDownloadListenerClient(listener: ResponseBodyListener): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.MINUTES)
.writeTimeout(0, TimeUnit.SECONDS)
.addNetworkInterceptor { chain ->
chain.proceed(chain.request()).also { originalResponse ->
originalResponse.body?.let { listener.update(it) }
}
}
.build()
}
in addNetworkInterceptor we get the response body as soon as it becomes available so that we track the actual download progress. It has stream of data being downloaded data from server.
Here is api interface
interface DownloadFilesApi {
#GET
#Streaming
suspend fun downloadFile(#Url url: String): ResponseBody
}
Here is the request itself
#OptIn(ExperimentalCoroutinesApi::class)
override suspend fun downloadFile(url: String, directory: File, fileName: String): Flow<DownloadFileState> =
callbackFlow {
val listener = ResponseBodyListener { responseBody: ResponseBody ->
this.launch {
responseBody.downloadToFileWithProgress(directory, fileName).collect {
trySend(it)
}
}
}
getDownloaderRetrofit(listener).create(DownloadFilesApi::class.java).downloadFile(url)
awaitClose()
}
Notice that downloadFile is suspend this suspends the coroutine until it finishes download.
here callbackFlow is used to work as bridge between the normal callback and flow result.
finally downloadToFileWithProgress is the same as written by #Robert the difference is that here it shows progress of downloading the file instead of showing progress of writing the file on desk after the actual download finishes.
for reference here it's RetrofitExtentions.kt
fun ResponseBody.downloadToFileWithProgress(directory: File, filename: String): Flow<DownloadFileState> = flow {
emit(DownloadFileState.Progress(0))
// flag to delete file if download errors or is cancelled
var deleteFile = true
val file = File(directory, filename)
try {
byteStream().use { inputStream ->
file.outputStream().use { outputStream ->
val totalBytes = contentLength()
val data = ByteArray(8_192)
var progressBytes = 0L
while (true) {
val bytes = inputStream.read(data)
if (bytes == -1) {
break
}
outputStream.write(data, 0, bytes)
progressBytes += bytes
val progress = ((progressBytes * 100) / totalBytes).toInt()
emit(DownloadFileState.Progress(percent = progress))
}
when {
progressBytes < totalBytes ->
throw Exception("missing bytes")
progressBytes > totalBytes ->
throw Exception("too many bytes")
else ->
deleteFile = false
}
}
}
emit(DownloadFileState.Finished(file))
} finally {
if (deleteFile) {
file.delete()
}
}
}
For completness here is how to get root folder in which you are going to save the video
fun videosRootFolder(context: Context): File {
return File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
APP_DIRECTORY_NAME
).run {
if (exists()) {
this
} else {
this.mkdir()
this
}
}
}
you can take a look here, you dont have to implement it by yourself ,the idea behind is to take the content-length of the request and when you write on the buffer just calculate your progress
Related
I have an object that can contains urls
class MyObject
(
var url_image:String? = null,
var url_document:String? = null,
var file_image:File? = null,
var file_document:File? = null
)
Also i have a method to download urls, this method returns Observable<File>
fun download_file(url:String): Observable<File>
{
//Some logic for creating file, downloading data from url and returning this file as Observable
}
I need to create a method where i would pass myObject, and if needed it will download it urls and finally return Observable<MyObject>. Something like this:
fun prepareForShare(obj: MyObject): Observable<MyObject>
{
return Observable.just(obj)
.map(
{
if (obj.url_image != null)
{
download_file(obj.url_image!!)
...
.subscribe(
{
obj.file_image = it
})
}
if (obj.url_document != null)
{
download_file(obj.url_image!!)
...
.subscribe(
{
obj.file_document = it
})
}
}))
}
How should i make this chain of requests in a right way?
You can combine all requests using the zip(...) operator and transform the MyObject in the flatMap{...} callback:
fun prepareForShare(obj: MyObject): Observable<MyObject> {
return Observable.zip(
if (obj.url_image != null) {
download_file(obj.url_image!!).flatMap {
obj.file_image = it
return#flatMap Observable.just(obj)
}
} else Observable.just(obj),
if (obj.url_document != null) {
download_file(obj.url_document!!).flatMap {
obj.file_document = it
return#flatMap Observable.just(obj)
}
} else Observable.just(obj),
BiFunction { o1, o2 ->
obj.file_image = o1.file_image
obj.file_document = o2.file_document
obj
}
)
}
Alternatively, for a more cleaner approach, you can wrap File in your custom holder object. For example:
data class MyFile(var file: File?)
and emit MyFile(null) if url is empty:
fun download_file(url:String?): Observable<MyFile>
{
if (url == null) return Observable.just(MyFile(null))
...
}
fun prepareForShare(obj: MyObject): Observable<MyObject> {
return Observable.zip(
download_file(obj.url_image),
download_file(obj.url_document),
BiFunction { file1, file2 ->
obj.file_image = file1.file
obj.file_document = file2.file
obj
}
)
}
I am new to android architecture components and I am trying to use LiveData and ViewModels with mvvm, repository pattern and retrofit. Referred to GitHubSample google gave in its architecture guide but want to simplify it little bit for my needs. Below is the code which I had so far but having below problems in completing it.
onActive() method in LiveDataCallAdapter is not invoking at all
Not able to figure out how I can get the response as a LiveData(I get this as null always) in SettingsData class? Ideally here I just want to have success and failure listener and I should get the data inside these blocks. All the generic network errors should already be handled before coming to this class. I am not able to figure out how to do this.
3.I do not want to call.enqueue in this SettingsData class which many examples shows
Any help is greatly appreciated. Thanks in advance
//Activity
private fun loadApplicationSettings() {
val settingsViewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java)
settingsViewModel.userApplicationSettings.observe(this, Observer<UserApplicationSettings> { userApplicationSettingsResult ->
Log.d("UserApplicationSettings", userApplicationSettingsResult.toString())
userSettingsTextView.text = userApplicationSettingsResult.isPushNotificationEnabled
})
}
//ViewModel
class SettingsViewModel : ViewModel() {
private var settingsRepository: SettingsRepository
lateinit var userApplicationSettings: LiveData<UserApplicationSettings>
init {
settingsRepository = SettingsRepository()
loadUserApplicationSettings()
}
private fun loadUserApplicationSettings() {
userApplicationSettings = settingsRepository.loadUserApplicationSettings()
}
}
//Repository
class SettingsRepository {
val settingsService = SettingsData()
fun loadUserApplicationSettings(): LiveData<UserApplicationSettings> {
return settingsService.getUserApplicationSettings()
}
}
//I do not want to do the network calls in repository, so created a seperate class gets the data from network call
class SettingsData {
val apiBaseProvider = ApiBaseProvider()
fun getUserApplicationSettings(): MutableLiveData<UserApplicationSettings> {
val userApplicationSettingsNetworkCall = apiBaseProvider.create().getApplicationSettings()
//Not sure how to get the data from userApplicationSettingsNetworkCall and convert it to livedata to give to repository
// deally here I just want to have success and failure listener and I should get the data inside these blocks. All the generic network errors should already be handled before coming to this class. I am not able to figure out how to do this.
val userApplicationSettingsData: LiveData<ApiResponse<UserApplicationSettings>> = userApplicationSettingsNetworkCall
//Thinking of having a success and fail block here and create a LiveData object to give to repository. Not sure how to do this
return userApplicationSettingsData
}
}
//Settings Service for retrofit
interface SettingsService {
#GET("url")
fun getApplicationSettings(): LiveData<ApiResponse<UserApplicationSettings>>
}
//Base provider of retrofit
class ApiBaseProvider {
fun create(): SettingsService {
val gson = GsonBuilder().setLenient().create()
val okHttpClient = createOkHttpClient()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(LiveDataCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("url")
.build()
return retrofit.create(SettingsService::class.java)
}
}
//
class LiveDataCallAdapterFactory : Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (getRawType(returnType) != LiveData::class.java) {
return null
}
val observableType = getParameterUpperBound(0, returnType as ParameterizedType)
val rawObservableType = getRawType(observableType)
if (rawObservableType != ApiResponse::class.java) {
throw IllegalArgumentException("type must be a resource")
}
if (observableType !is ParameterizedType) {
throw IllegalArgumentException("resource must be parameterized")
}
val bodyType = getParameterUpperBound(0, observableType)
return LiveDataCallAdapter<Any>(bodyType)
}
}
//Custom adapter that does the network call
class LiveDataCallAdapter<T>(private val responseType: Type) : CallAdapter<T, LiveData<ApiResponse<T>>> {
override fun responseType(): Type {
return responseType
}
override fun adapt(call: Call<T>): LiveData<ApiResponse<T>> {
return object : LiveData<ApiResponse<T>>() {
override fun onActive() {
super.onActive()
call.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
println("testing response: " + response.body())
postValue(ApiResponse.create(response))
}
override fun onFailure(call: Call<T>, throwable: Throwable) {
postValue(ApiResponse.create(throwable))
}
})
}
}
}
}
//I want to make this class as a generic class to do all the network success and error handling and then pass the final response back
/**
* Common class used by API responses.
* #param <T> the type of the response object
</T> */
sealed class ApiResponse<T> {
companion object {
fun <T> create(error: Throwable): ApiErrorResponse<T> {
return ApiErrorResponse(error.message ?: "unknown error")
}
fun <T> create(response: Response<T>): ApiResponse<T> {
println("testing api response in create")
return if (response.isSuccessful) {
val body = response.body()
if (body == null || response.code() == 204) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(
body = body
)
}
} else {
val msg = response.errorBody()?.string()
val errorMsg = if (msg.isNullOrEmpty()) {
response.message()
} else {
msg
}
ApiErrorResponse(errorMsg ?: "unknown error")
}
}
}
}
/**
* separate class for HTTP 204 responses so that we can make ApiSuccessResponse's body non-null.
*/
class ApiEmptyResponse<T> : ApiResponse<T>()
data class ApiErrorResponse<T>(val errorMessage: String) : ApiResponse<T>()
data class ApiSuccessResponse<T>(
val body: T
) : ApiResponse<T>() {
}
We can connect Activity/Fragment and ViewModel as below:
Firstly, we have to create our ApiResource which will handle the retrofit response.
public class ApiResource<T> {
#NonNull
private final Status status;
#Nullable
private final T data;
#Nullable
private final ErrorResponse errorResponse;
#Nullable
private final String errorMessage;
private ApiResource(Status status, #Nullable T data, #Nullable ErrorResponse errorResponse, #Nullable String errorMessage) {
this.status = status;
this.data = data;
this.errorResponse = errorResponse;
this.errorMessage = errorMessage;
}
public static <T> ApiResource<T> create(Response<T> response) {
if (!response.isSuccessful()) {
try {
JSONObject jsonObject = new JSONObject(response.errorBody().string());
ErrorResponse errorResponse = new Gson()
.fromJson(jsonObject.toString(), ErrorResponse.class);
return new ApiResource<>(Status.ERROR, null, errorResponse, "Something went wrong.");
} catch (IOException | JSONException e) {
return new ApiResource<>(Status.ERROR, null, null, "Response Unreachable");
}
}
return new ApiResource<>(Status.SUCCESS, response.body(), null, null);
}
public static <T> ApiResource<T> failure(String error) {
return new ApiResource<>(Status.ERROR, null, null, error);
}
public static <T> ApiResource<T> loading() {
return new ApiResource<>(Status.LOADING, null, null, null);
}
#NonNull
public Status getStatus() {
return status;
}
#Nullable
public T getData() {
return data;
}
#Nullable
public ErrorResponse getErrorResponse() {
return errorResponse;
}
#Nullable
public String getErrorMessage() {
return errorMessage;
}
}
The Status is just an Enum class as below:
public enum Status {
SUCCESS, ERROR, LOADING
}
The ErrorResponse class must be created in such a way that the getter and setter can handle the error.
RetrofitLiveData class
public class RetrofitLiveData<T> extends LiveData<ApiResource<T>> {
private Call<T> call;
public RetrofitLiveData(Call<T> call) {
this.call = call;
setValue(ApiResource.loading());
}
Callback<T> callback = new Callback<T>() {
#Override
public void onResponse(Call<T> call, Response<T> response) {
setValue(ApiResource.create(response));
}
#Override
public void onFailure(Call<T> call, Throwable t) {
setValue(ApiResource.failure(t.getMessage()));
}
};
#Override
protected void onActive() {
super.onActive();
call.enqueue(callback);
}
#Override
protected void onInactive() {
super.onInactive();
if (!hasActiveObservers()) {
if (!call.isCanceled()) {
call.cancel();
}
}
}
}
Repository class
public class Repository {
public LiveData<ApiResource<JunoBalanceResponse>> getJunoBalanceResponse(Map<String, String> headers) {
return new RetrofitLiveData<>(ApiClient.getJunoApi(ApiClient.BASE_URL.BASE).getJunoBalance(headers));
}
}
JunoBalanceResponse contains the objects and its getters and setters that I am waiting as a response of my retrofit request.
below is an example for the api interface.
public interface JunoApi {
#Headers({"X-API-Version: 2"})
#GET("balance")
Call<JunoBalanceResponse> getJunoBalance(#HeaderMap Map<String, String> headers);
}
ApiClient class
public class ApiClient {
public enum BASE_URL {
AUTH, BASE
}
private static Retrofit retrofit;
private static final String JUNO_SANDBOX_AUTH_URL = "https://sandbox.boletobancario.com/authorization-server/";
private static final String JUNO_SANDBOX_BASE_URL = "https://sandbox.boletobancario.com/api-integration/";
private static Retrofit getRetrofit(String baseUrl) {
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS)
.writeTimeout(90, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit;
}
public static JunoApi getJunoApi(BASE_URL targetPath) {
switch (targetPath) {
case AUTH: return getRetrofit(JUNO_SANDBOX_AUTH_URL).create(JunoApi.class);
case BASE: return getRetrofit(JUNO_SANDBOX_BASE_URL).create(JunoApi.class);
default: return getRetrofit(JUNO_SANDBOX_BASE_URL).create(JunoApi.class);
}
}
}
Now we can connect our Repository and ApiViewModel.
public class ApiViewModel extends ViewModel {
private Repository repository = new Repository();
public LiveData<ApiResource<JunoBalanceResponse>> getJunoBalanceResponse(Map<String, String> headers) {
return repository.getJunoBalanceResponse(headers);
}
}
And finally, we can observe the retrofit response in our Activity/Fragment
apiViewModel = ViewModelProviders.of(requireActivity()).get(ApiViewModel.class);
apiViewModel.getJunoBalanceResponse(headers).observe(getViewLifecycleOwner(), new Observer<ApiResource<JunoBalanceResponse>>() {
#Override
public void onChanged(ApiResource<JunoBalanceResponse> response) {
switch (response.getStatus()) {
case LOADING:
Log.i(TAG, "onChanged: BALANCE LOADING");
break;
case SUCCESS:
Log.i(TAG, "onChanged: BALANCE SUCCESS");
break;
case ERROR:
Log.i(TAG, "onChanged: BALANCE ERROR");
break;
}
}
});
I'm trying to create an App which can receive data and send data to the microcontroller (ESP32). But for some reason, I'm unable to receive data from microcontroller successfully.
The app is written in Kotlin, and I already tried some examples mentioned on StackOverflow, but none of them actually works on my code.
I can successfully send data to the microcontroller via Bluetooth, but I can't receive data from Bluetooth. (The method I used in the microcontroller is just simply "ESP_BT.println("Check");"
In the code snippet, the function relates to my receiving data is called "receiveBluetooth"
class ControlActivity: AppCompatActivity() {
companion object {
val myUUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
var myBluetoothSocket: BluetoothSocket? = null
lateinit var myProgress: ProgressDialog
lateinit var myBluetoothAdapter: BluetoothAdapter
var myIsConnected: Boolean = false
lateinit var myAddress: String
val mmInStream: InputStream? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.control_layout)
myAddress = intent.getStringExtra(SelectDeviceActivity.EXTRA_ADDRESS)
ConnectToDevice(this).execute()
val btnShow = findViewById<Button>(R.id.btnShow)
var inputRPM: String
//Read in value and store it as String
btnShow.setOnClickListener{
inputRPM = receiveInput()
sendCommand(inputRPM)
}
//Read RPM from microcontroller (bluetooth)
val showCountTextView = findViewById<TextView>(R.id.textView)
btnRefresh.setOnClickListener {
//showCountTextView.text = receiveBluetooth()
receiveBluetooth(showCountTextView)
}
control_disconnect.setOnClickListener{
disconnect()
}
}
private fun receiveInput(): String {
val input = findViewById<EditText>(R.id.editText)
return input.text.toString()
}
private fun sendCommand(input: String) {
if (myBluetoothSocket != null) {
try{
myBluetoothSocket!!.outputStream.write(input.toByteArray())
} catch (e: IOException) {
e.printStackTrace()
}
}
}
private fun receiveBluetooth(input: TextView) {
val buffer = ByteArray(256)
val bytes:Int
var tmpIn: InputStream? = null
if (myBluetoothSocket != null) {
try {
tmpIn = myBluetoothSocket!!.inputStream
val mmInStream = DataInputStream(tmpIn)
bytes = mmInStream.read(buffer)
val readMessage = String(buffer, 0, bytes)
input.text = readMessage
//input.text="123"
} catch (e:IOException) {
e.printStackTrace()
}
}
}
private fun disconnect() {
if (myBluetoothSocket != null) {
try {
myBluetoothSocket!!.close()
myBluetoothSocket = null
myIsConnected = false
} catch (e: IOException) {
e.printStackTrace()
}
}
finish()
}
private class ConnectToDevice(c: Context) : AsyncTask<Void, Void, String> () {
private var connectSuccess: Boolean = true
private val context: Context
init {
this.context = c
}
override fun onPreExecute() {
super.onPreExecute()
myProgress = ProgressDialog.show(context, "Connecting", "Please wait")
}
override fun doInBackground(vararg params: Void?): String? {
try {
if (myBluetoothSocket == null || !myIsConnected) {
myBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val device: BluetoothDevice = myBluetoothAdapter.getRemoteDevice(myAddress)
myBluetoothSocket = device.createInsecureRfcommSocketToServiceRecord(myUUID)
BluetoothAdapter.getDefaultAdapter().cancelDiscovery()
myBluetoothSocket!!.connect()
}
} catch (e: IOException) {
connectSuccess = false
e.printStackTrace()
}
//Needs be fixed
return null
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
if (!connectSuccess) {
Log.i("data", "couldn't connect")
} else {
myIsConnected = true
}
myProgress.dismiss()
}
}
}
I expect the text will show exactly "Check", but instead, my text will only show the initial value that I assigned.
Maybe you should use a library. For me works fine RxAndroidBle library:
Gradle:
implementation "com.polidea.rxandroidble2:rxandroidble:1.8.1"
Implementation:
In my project with Android Java and ESP32 too, I read some characteristics or values with simple implementations, for example:
public void setupNotification() {
if (isConnected()) {
final Disposable disposable = connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(charactSensorDataUuid))
.doOnNext(notificationObservable -> { notificationHasBeenSetUp(); })
.flatMap(notificationObservable -> notificationObservable)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onNotificationReceived, this::onNotificationSetupFailure);
compositeDisposable.add(disposable);
}
}
public void readSensorConfig(){
if (isConnected()) {
final Disposable disposable = connectionObservable
.firstOrError()
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(charactConfigUuid))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSensorConfigRead, this::onReadFailure);
compositeDisposable.add(disposable);
}
}
public void readSensorData(){
if (isConnected()) {
final Disposable disposable = connectionObservable
.firstOrError()
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(charactSensorDataUuid))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSensorDataRead, this::onReadFailure);
compositeDisposable.add(disposable);
}
}
The complete Java implementation is here:
https://github.com/kike-canaries/android-hpma115s0/blob/master/app/src/main/java/hpsaturn/pollutionreporter/common/BLEHandler.java
The migration to Kotlin should be simple, also on this library the main target is Bluetooth BLE, and they have many samples on Kotlin
I am trying to stream a video from websocket url in android. The url looks something like this ws://abc.com:80/api/streaming/download/mp4/ . I am not really sure how to proceed because this thing is new for me. I tried searching on the internet and i found only one solution on stackoverflow which says to connect with websocket using okhttp and then use okhttpdatasource with Exoplayer. So I tried connecting to websocket url using okhttp and i am successfully recieving bytestream. Here is the code:
public void startListen() {
// Request request = new Request.Builder().url(mContext.getResources().getString(R.string.ws_url)).build();
Request request = new Request.Builder().url(getApplicationContext().getResources().getString(R.string.myUrl)).build();
//final ByteBuffer buf = ByteBuffer.allocate(MediaBlockSize);
mWebSocket = mOkHttpClient.newWebSocket(request, new WebSocketListener() {
#Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
Log.d("MNMN", String.valueOf(response));
}
#Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
Log.d("MNMN", "text = " + text);
}
#Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
//Log.d("MNMNx", "size = " + bytes.size());
final byte b[] = bytes.toByteArray();
try {
mOutputStream.write(b);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onClosing(WebSocket webSocket, int code, String reason) {
super.onClosing(webSocket, code, reason);
}
#Override
public void onClosed(WebSocket webSocket, int code, String reason) {
super.onClosed(webSocket, code, reason);
}
#Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
Log.d("MNMN", String.valueOf(response));
}
});
}
But i don't really know how to make it work with Exoplayer. Exoplayer has extension for okhttpdatasource but i didn't find any good tutorial of using it. Can someone guide me how can I use stream received from okhttp with exoplayer to play the video?
I know its late to answer. But in case someone else looking for this.
I had similar situation. I don't think OkHttpDataSource is meant for wss.
I came up with my own Exo DataSource that works as expected.
First collect the data received from wss using OkHttp's WebSocketListener
class WssDataStreamCollector #Inject constructor() : WebSocketListener() {
private val wssData = ConcurrentSkipListSet<ByteString>()
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
wssData.add(bytes)
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
super.onClosing(webSocket, code, reason)
wssData.removeAll(wssData)
}
fun canStream(): Boolean {
return wssData.size > 0
}
fun getNextStream(): ByteString {
return wssData.pollFirst()
}
}
Create the DataSource
class WssStreamDataSource : BaseDataSource(true) {
#Inject
lateinit var httpClient: OkHttpClient
#Inject
lateinit var dataStreamCollector: WssDataStreamCollector
var webSocketClient: WebSocket? = null
private var currentByteStream: ByteArray? = null
private var currentPosition = 0;
private var remainingBytes = 0;
override fun open(dataSpec: DataSpec): Long {
// Form the request and open the socket.
// Provide the listener
// which collects the data for us (Previous class).
webSocketClient = httpClient.newWebSocket(
Request.Builder().apply {
dataSpec.httpRequestHeaders.forEach { entry ->
addHeader(entry.key, entry.value)
}
}.url(dataSpec.uri.toString()).build(),
dataStreamCollector
)
return -1 // Return -1 as the size is unknown (streaming)
}
override fun getUri(): Uri? {
webSocketClient?.request()?.url?.let {
return Uri.parse(it.toString())
}
return null
}
override fun read(target: ByteArray, offset: Int, length: Int): Int {
// return 0 (nothing read) when no data present...
if (currentByteStream == null && !dataStreamCollector.canStream()) {
return 0
}
// parse one (data) ByteString at a time.
// reset the current position and remaining bytes
// for every new data
if (currentByteStream == null) {
currentByteStream = dataStreamCollector.getNextStream().toByteArray()
currentPosition = 0
remainingBytes = currentByteStream?.size ?: 0
}
val readSize = min(length, remainingBytes)
currentByteStream?.copyInto(target, offset, currentPosition, currentPosition + readSize)
currentPosition += readSize
remainingBytes -= readSize
// once the data is read set currentByteStream to null
// so the next data would be collected to process in next
// iteration.
if (remainingBytes == 0) {
currentByteStream = null
}
return readSize
}
override fun close() {
// close the socket and relase the resources
webSocketClient?.cancel()
}
// Factory class for DataSource
class Factory : DataSource.Factory {
override fun createDataSource(): DataSource = WssStreamDataSource()
}
}
That's all, you are good to go.
Now use ProgressiveMediaSource with the DataSource we created like below.
SimpleExoPlayer(yourBuilder).apply {
setVideoSurfaceView(surfaceView)
val mediaItem = MediaItem.fromUri(uri) // URI with wss://
val factory = ProgressiveMediaSource.Factory(WssStreamDataSource.Factory())
addMediaSource(factory.createMediaSource(mediaItem))
prepare()
playWhenReady = true
play()
}
Here i got a sample of code in presenter. How do i make write a test for onSuccess and onFailure in retrofit call
public void getNotifications(final List<HashMap<String,Object>> notifications){
if (!"".equalsIgnoreCase(userDB.getValueFromSqlite("email",1))) {
UserNotifications userNotifications =
new UserNotifications(userDB.getValueFromSqlite("email",1),Integer.parseInt(userDB.getValueFromSqlite("userId",1).trim()));
Call call = apiInterface.getNotifications(userNotifications);
call.enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) {
UserNotifications userNotifications1 = (UserNotifications) response.body();
if(userNotifications1.getNotifications().isEmpty()){
view.setListToAdapter(notifications);
onFailure(call,new Throwable());
}
else {
for (UserNotifications.Datum datum:userNotifications1.getNotifications()) {
HashMap<String,Object> singleNotification= new HashMap<>();
singleNotification.put("notification",datum.getNotification());
singleNotification.put("date",datum.getDate());
notifications.add(singleNotification);
}
view.setListToAdapter(notifications);
}
}
#Override
public void onFailure(Call call, Throwable t) {
call.cancel();
}
});
}
}
}
How do i write unittesting to cover all cases for this piece of code.
Thanks
When you want to test different responses from service (API) it's probably best to mock it and return what you need.
#Test
public void testApiResponse() {
ApiInterface mockedApiInterface = Mockito.mock(ApiInterface.class);
Call<UserNotifications> mockedCall = Mockito.mock(Call.class);
Mockito.when(mockedApiInterface.getNotifications()).thenReturn(mockedCall);
Mockito.doAnswer(new Answer() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Callback<UserNotifications> callback = invocation.getArgumentAt(0, Callback.class);
callback.onResponse(mockedCall, Response.success(new UserNotifications()));
// or callback.onResponse(mockedCall, Response.error(404. ...);
// or callback.onFailure(mockedCall, new IOException());
return null;
}
}).when(mockedCall).enqueue(any(Callback.class));
// inject mocked ApiInterface to your presenter
// and then mock view and verify calls (and eventually use ArgumentCaptor to access call parameters)
}
For those looking for an answer using Kotlin and MockK:
Assume you have something like this:
class ApiService(private val client: OkHttpClient) {
fun makeApiCall() {
val url = "https://someendpoint.com.br/"
val request = Request.Builder().url(url).build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, exception: IOException) {
//Logic to handle Failure
}
override fun onResponse(call: Call, response: Response) {
//Logic to handle Success
}
})
}
}
You can test this using Junit 5 and mockK
class ApiServiceTest {
private lateinit var client: okhttp3.OkHttpClient
private lateinit var apiService: ApiService
#BeforeEach
fun setup() {
// Setup a new mock for each test case
client = mockk(relaxed = true)
apiService = ApiService(client)
}
#Test
fun `test with mockedCallback`() {
val mockedCall = mockk<Call>(relaxed = true)
every { mockedCall.enqueue(any()) } answers {
//Get the callback from the arguments
val callback = args[0] as Callback
// Create a fakeRequest
val fakeRequest = okhttp3.Request.Builder().url("https://someendpoint.com.br/").build()
// Create the response you need to test, it can be failure or success
val errorResponse = Response.Builder()
.request(fakeRequest)
.protocol(Protocol.HTTP_1_1)
.code(400)
.message("Error")
.build()
//Call the callback with the mocked response you created.
callback.onResponse(mockedCall, errorResponse)
}
// Setup the client mock to return the mocked call you created
every {
client.newCall(any())
} returns mockedCall
apiService.makeApiCall()
// Verify whatever you need to test your logic to handle each case.
}
}