I noticed that when I create a separate Java file in my android studio project
and run it. It doesn't run like in a normal java project. I wanted to write
code for making HTTP call and read the response. Before integrating into the app
I need to test it out. One way could be to open Intellij Idea and write
a completely different small java project and then put that code inside the android
app.
There are several ways to do this. You can use Retrofit or okhttp.
using volley is one of the easiest ways:
implementation 'com.android.volley:volley:1.2.0'
manifest:
<uses-permission android:name="android.permission.INTERNET" />
in the fragment/activity/viewModel:
private var USGS_REQUEST_URL =
"example.com"
fun getResponse() {
volleySingleton = VolleySingleton.getInstance(application)
}
val stringRequest = StringRequest(
Request.Method.GET, REQUEST_URL,
{ response ->
//Handel your result
},
{
Log.d(TAG, "error")
}).setRetryPolicy(
DefaultRetryPolicy(
DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, 3,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
).setShouldCache(true)
volleySingleton!!.addToRequestQueue(stringRequest)
VolleySingleton :
class VolleySingleton constructor(context: Context){
companion object {
/*
#Volatile: meaning that writes to this field are immediately made visible to other threads.
*/
#Volatile
private var instance: VolleySingleton? = null
fun getInstance(context: Context) = instance?: synchronized(this){
instance?: VolleySingleton(context)
}
}
/*
by lazy: requestQueue won't be initialized until this method gets called
*/
val requestQueue: RequestQueue by lazy {
// applicationContext is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in.
Volley.newRequestQueue(context.applicationContext)
}
fun <T> addToRequestQueue(req: Request<T>){
requestQueue.add(req)
}}
Related
public final fun sendDeviceToken() {
FirebaseMessaging.getInstance().token.addOnCompleteListener {
if (!it.isSuccessful) {
return#addOnCompleteListener
}
val token = it.result //this is the token retrieved
val queue = Volley.newRequestQueue(this)
val url = "https://example.com"
val requestBody = "token=$token"
val stringReq : StringRequest =
object : StringRequest(Method.POST, url,
Response.Listener { response ->
// response
var strResp = response.toString()
Log.d("API", strResp)
},
Response.ErrorListener { error ->
Log.d("API", "error => $error")
}
){
override fun getBody(): ByteArray {
return requestBody.toByteArray(Charset.defaultCharset())
}
}
queue.add(stringReq)
}
}
I am newbie in Kotlin and trying to send devicetoken to server
When I call MainActivity().sendDeviceToken() function I get Attempt to invoke virtual method 'android.content.Context android.content.Context.getApplicationContext()' on a null object reference
How can I pass context to newRequestQueue?
Check the whole code here https://codefile.io/f/uhfe84zzxexSDBp1GBSG
Activities only have a Context when the system has created them, and they've reached the CREATED state of their lifecycle (i.e. onCreate has been called). So you can only use an Activity as a Context (like in your newRequestQueue(this) call) once it's in that CREATED state.
It's the system handling an Activity's creation and setup and callbacks that makes it work as an Activity component. Without that, it's just a plain object - and that's all you get if you construct one yourself, with MainActivity(). Since the usefulness of an Activity is how it interacts with the system, there's basically no reason to ever construct one yourself, because it won't really "work".
If you want to use that method outside of an Activity, add a Context parameter and use that in your newRequestQueue call.
public final fun sendDeviceToken(context: Context) {
...
val queue = Volley.newRequestQueue(context)
Then call it from somewhere you do have access to a Context. And ideally, if you're really not calling this from an Activity or Fragment, it shouldn't be inside an Activity class at all - you shouldn't need to construct one specially to call this method, basically.
I'm finding this exception related with Moshi sometimes when opening the app:
Caused by java.lang.ArrayIndexOutOfBoundsException: length=33; index=33
at java.util.ArrayList.add(ArrayList.java:468)
at com.squareup.moshi.Moshi$Builder.add(Moshi.java:231)
We initialise a repository in the BaseApplication which, sometimes, results in the mentioned crash when initialising Moshi. I'm finding this error in the app reports but I'm not able to reproduce it. Let's jump to the what we have and see if you might have a clue on it.
This factory is used to create Moshi instances, getting the crash when adding KotlinJsonAdapterFactory:
object MyMoshiConverterFactory {
fun create(setup: (Moshi.Builder.() -> Unit)? = null): Converter.Factory {
val moshi = MoshiUtil.createMoshi()
setup?.let { moshi.it() }
moshi.add(KotlinJsonAdapterFactory()) // Here is the crash!
return MoshiConverterFactory.create(moshi.build())
}
}
Here we have a class where we have all the converters we use. It really has a lot more of converters, but I've removed a few of them for simplicity:
object MoshiUtil {
private val lazyMoshi by lazy {
Moshi.Builder().apply {
add(DateAdapter())
add(DefaultOnDataMismatchAdapter.newFactory(FeedItem::class.java, null))
add(SkipListNullValuesAdapter.createFactory(Element::class.java))
add(SkipListNullValuesAdapter.createFactory(Post::class.java))
add(SkipListNullValuesAdapter.createFactory(MetadataItem::class.java))
add(GeoGeometry::class.java, GeometryAdapter())
}
}
fun createMoshi() = lazyMoshi
}
And finally, in our BaseApplication, we have something like this:
class BaseApplication {
#Override
public void onCreate() {
super.onCreate();
val myService = getMyService(applicationContext)
}
private fun getMyService(appContext: Context): MyService {
val converterFactory = MyMoshiConverterFactory.create()
return Retrofit.Builder().baseUrl(baseUrl).apply {
addConverterFactory(converterFactory)
client(okHttpClientBuilder.build())
}.build().create(MyService::class.java)
}
}
}
So, do you see anything that could be causing it? Do you think it might be a concurrency issue happening at startup when the several places in the app are creating the MoshiUtils object at the same time?. Looking forward to hear from you guys, thanks!
Moshi.Builder is mutable and not thread-safe, so this error you're getting sometimes is a race condition as a result of that. You should call .build() on that base MoshiUtil instance to get an immutable Moshi instance, then make the return value of MoshiUtil.createMoshi be moshi.newBuilder() (creates a Moshi.Builder already configured like the existing Moshi instance), like so:
object MoshiUtil {
private val baseMoshi: Moshi = Moshi.Builder().apply {
// ...
}.build()
fun createMoshi(): Moshi.Builder = baseMoshi.newBuilder()
}
Since every person that calls createMoshi now gets their own instance of Moshi.Builder, there shouldn't be any concurrency problems anymore.
I am running across weird behaviors with HttpLoggingInterceptor. I have noticed that if I use newBuilder() the logging does not work.
// instantiate object (in app)
val okHttpRequestManager: HttpRequestManager = OkHttpRequestManager(OkHttpClient(), null)
// execute request (in app)
okHttpRequestManager.execute(request, callback)
// another class (in request module)
open class OkHttpRequestManager(private val client: OkHttpClient,
private val httpLoggingInterceptor: HttpLoggingInterceptor?) : HttpRequestExecutor {
override fun execute(httpRequest: HttpRequest, callback: HttpResponseCallback?) {
if (httpLoggingInterceptor != null) {
client.newBuilder().addInterceptor(httpLoggingInterceptor).build()
}
// perform request below
...
}
}
The above code snippet does not work. However, if I make my parameter a builder, everything works fine. Is using newBuilder() the incorrect way to do this?
// the below works
// another class (in request module)
open class OkHttpRequestManager(private val client: OkHttpClient.Builder,
private val httpLoggingInterceptor: HttpLoggingInterceptor?) : HttpRequestExecutor {
override fun execute(httpRequest: HttpRequest, callback: HttpResponseCallback?) {
if (httpLoggingInterceptor != null) {
// no newBuilder() or build() and works like a charm
client.addInterceptor(httpLoggingInterceptor)
}
// perform request below
...
}
}
Anyone have an idea as to why this is?
That's because the method newBuilder() as the name implies, returns the new builder object and when you call build() on it, new instance of OkHttpClient will be returned created from the new builder.
Here is the source code:
/** Prepares the [request] to be executed at some point in the future. */
override fun newCall(request: Request): Call {
return RealCall.newRealCall(this, request, forWebSocket = false)
}
build() method
fun build(): OkHttpClient = OkHttpClient(this)
The newBuilder adds to the attributes of the existing client so you
will have a new client with both the old and new attributes.
If you want to use newBuilder() method then you need to make use of the newly created OkHttpClient.
// another class (in request module)
open class OkHttpRequestManager(private val client: OkHttpClient,
private val httpLoggingInterceptor: HttpLoggingInterceptor?) : HttpRequestExecutor {
override fun execute(httpRequest: HttpRequest, callback: HttpResponseCallback?) {
if (httpLoggingInterceptor != null) {
val newClient = client.newBuilder().addInterceptor(httpLoggingInterceptor).build()
}
// perform request below using newClient
...
}
}
I am using Volley image request inside recyclerview adapter.
Request appears to work fine until a fast scroll is done, whenever I scroll the recyclerview fast up or down, the app crashes with following error :
java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Permission denied. See process maps in the log.
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:883)
at com.android.volley.RequestQueue.start(RequestQueue.java:134)
at com.android.volley.toolbox.Volley.newRequestQueue(Volley.java:91)
at com.android.volley.toolbox.Volley.newRequestQueue(Volley.java:67)
at com.android.volley.toolbox.Volley.newRequestQueue(Volley.java:102)
at com.squadtechs.hdwallpapercollection.main_activity.fragment.WallpaperAdapter.populateViews(WallpaperAdapter.kt:60)
at com.squadtechs.hdwallpapercollection.main_activity.fragment.WallpaperAdapter.onBindViewHolder(WallpaperAdapter.kt:38)
at com.squadtechs.hdwallpapercollection.main_activity.fragment.WallpaperAdapter.onBindViewHolder(WallpaperAdapter.kt:21)
Following is my onBindViewHolder() code:
override fun onBindViewHolder(holder: WallpaperHolder, position: Int) {
populateViews(holder, position)
}
private fun populateViews(holder: WallpaperHolder, position: Int) {
val requestQueue = Volley.newRequestQueue(context)
val imageRequest = ImageRequest(
list[position].wallpaper_image_url,
Response.Listener { response ->
holder.imgGrid.scaleType = ImageView.ScaleType.CENTER
holder.imgGrid.setImageBitmap(response)
holder.progress.visibility = View.GONE
},
1024,
860,
ImageView.ScaleType.CENTER,
null,
Response.ErrorListener { error ->
Toast.makeText(context, "Error loading Image", Toast.LENGTH_LONG).show()
holder.progress.visibility = View.GONE
}).setRetryPolicy(
DefaultRetryPolicy(
20000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
)
requestQueue.add(imageRequest)
holder.txtCategory.visibility = View.GONE
}
According to log, error is thrown at line where request queue is declared, i.e val requestQueue = Volley.newRequestQueue(context)
Remember: the app works fine when scroled normally but crashes when scrolled fast
Your recycler view will fire your adapter's onBindViewHolder every time an element is supposed to be displayed that was not bound to a view before (or that was un-bound).
When you scroll fast, binds and unbinds will happen fast. Each bind generates an HTTP request, which is a relatively expensive IO operation that consumes memory.
This is a recipe for disaster. Do not send HTTP requests based on a regular user interaction like this. If someone keeps scrolling up and down, the app is guaranteed to run out of memory.
Instead, think of a better strategy. Possibly pre-load data asynchronously, or at least cache data once loaded.
#fjc pointed out correct the HTTP request is resource intensive. If you look at your populateViews function's first line
val requestQueue = Volley.newRequestQueue(context)
This is the main reason of OOM. You are creating multiple request queue for each image request which is therefore occupying all the resources thus result in OOM. In order to overcome that you need to use a single requestqueue for all of your application. it is also recommended by Google to use a Singleton class for handling the requestqueue.DOC
if your application makes constant use of the network, it's probably
most efficient to set up a single instance of RequestQueue that will
last the lifetime of your app. You can achieve this in various ways.
The recommended approach is to implement a singleton class that
encapsulates RequestQueue and other Volley functionality. Another
approach is to subclass Application and set up the RequestQueue in
Application.onCreate(). But this approach is discouraged; a static
singleton can provide the same functionality in a more modular way.
A quick way to solve your problem is to copy the following class in your project
class MySingleton constructor(context: Context) {
companion object {
#Volatile
private var INSTANCE: MySingleton? = null
fun getInstance(context: Context) =
INSTANCE ?: synchronized(this) {
INSTANCE ?: MySingleton(context).also {
INSTANCE = it
}
}
}
val imageLoader: ImageLoader by lazy {
ImageLoader(requestQueue,
object : ImageLoader.ImageCache {
private val cache = LruCache<String, Bitmap>(20)
override fun getBitmap(url: String): Bitmap {
return cache.get(url)
}
override fun putBitmap(url: String, bitmap: Bitmap) {
cache.put(url, bitmap)
}
})
}
val requestQueue: RequestQueue by lazy {
// applicationContext is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in.
Volley.newRequestQueue(context.applicationContext)
}
fun <T> addToRequestQueue(req: Request<T>) {
requestQueue.add(req)
}
}
Replace the first line of your populateViews function with
val requestQueue = MySingleton.getInstance(context).requestQueue
This should solve your problem
Another way is to use NetworkImageView from Volley's ToolBox
How to use
Replace your ImageView with NetworkImageView
<com.android.volley.toolbox.NetworkImageView
android:id="#+id/imgGrid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerInside"
/>
and load the image using
holder.imgGrid.setImageUrl(list[position].wallpaper_image_url,MySingleton.getInstance(context).imageLoader);
When I use
BuildConfig.DEBUG
in Kotlin I get this error:
expecting member declaratuon
My code:
class API {
companion object {
private lateinit var instance: Retrofit
private const val baseUrl = baseURL
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
builder.addInterceptor(interceptor);
}
}
You can't use an if statement as a top level declaration like that, you have to declare it inside a function or init block.
So something like this, perhaps:
class API {
companion object {
private lateinit var instance: Retrofit
private const val baseUrl = baseURL
init {
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
builder.addInterceptor(interceptor);
}
}
}
}
You're making a call outside a function or constructor. You cannot have if-statements outside method bodies, which applies to both Kotlin and Java.
objects are classes too, all though they follow the singleton pattern. You still can't put if-statements outside method bodies. The class-level declarations can only contain methods, constructors, and fields, and some blocks (i.e. init), not if-statements and calls to the defined variables.
In addition, you're using Java syntax which won't compile at all. Use Kotlin syntax instead and move it to an init block inside the companion object.
The init block is called like initialization when the companion object is initialized.
companion object{
//Other declarations
init{
if (BuildConfig.DEBUG) {
var interceptor = HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
builder.addInterceptor(interceptor);//I have no clue where you define builder, but I'm assuming you've done it *somewhere* and just left it out of the question
}
}
}