I need to inject different instances of classes in my various JUnit tests that use Koin. When run one at a time, the tests pass, but when run all together, they fail. Test foo is producing values that could only come from the objected injected in test bar, so I must not be properly isolating Koin. The actual tests are more complicated integration tests, and the sample below was my attempt to make something self-contained that reproduces the issue, but unfortunately these tests pass when run together. The structure is the same, though.
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.core.context.GlobalContext.startKoin
import org.koin.core.context.GlobalContext.stopKoin
import org.koin.test.KoinTest
import org.koin.test.mock.declare
import kotlin.time.Duration.Companion.seconds
class KoinSampleTest : KoinTest {
#Before
fun setup() {
startKoin { }
}
#After
fun teardown() = stopKoin()
#Test
fun foo() = runBlocking<Unit> {
val namedUser = Foo()
declare<Named> { namedUser }
val user = NamedUser()
assertThat(user.sayName()).isEqualTo("foo")
}
#Test
fun bar() = runBlocking<Unit> {
val namedUser = Bar()
declare<Named> { namedUser }
val user = NamedUser()
assertThat(user.sayName()).isEqualTo("bar")
}
}
interface Named {
val name: String
}
class Foo(override val name: String = "foo") : Named
class Bar(override val name: String = "bar") : Named
class NamedUser : KoinComponent {
val nameContainer by inject<Named>()
suspend fun sayName(): String {
delay(2.seconds) // simulate some latency of in-process RPC
return nameContainer.name
}
}
Are there some best practices for KoinTest that I'm missing here?
The answer: The class with the injected field can't be a singleton, or Kotlin will do what you asked it to and only ever instantiate the field once.
package com.laptopera.control
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Ignore
import org.junit.Before
import org.junit.Test
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.core.context.GlobalContext.startKoin
import org.koin.core.context.GlobalContext.stopKoin
import org.koin.test.KoinTest
import org.koin.test.mock.declare
import kotlin.time.Duration.Companion.seconds
class KoinSampleTest : KoinTest {
#Before fun setup() { startKoin { } }
#After fun teardown() = stopKoin()
#Test
fun foo() = runBlocking<Unit> {
val namedUser = Foo()
declare<Named> { namedUser }
val user = NamedUser
assertThat(user.sayName()).isEqualTo("foo")
}
#Test
fun bar() = runBlocking<Unit> {
val namedUser = Bar()
declare<Named> { namedUser }
val user = NamedUser
assertThat(user.sayName()).isEqualTo("bar")
}
}
interface Named { val name: String }
class Foo(override val name: String = "foo") : Named
class Bar(override val name: String = "bar") : Named
// The test fails if this is an object, and passes if it is a class, because nameContainer is only ever instantiated
// once per runtime in a singleton/object.
object NamedUser : KoinComponent {
val nameContainer by inject<Named>()
suspend fun sayName(): String {
delay(2.seconds)
return nameContainer.name
}
}
Related
this project observation internet connectivity ,how i convert code from composablle activity to empty activity what instant of collectAsState for empty activity
get code form video youtube observation internet connectivity video
interface observeconnectivity
package com.plcoding.observeconnectivity
import kotlinx.coroutines.flow.Flow
interface ConnectivityObserver {
fun observe(): Flow<Status>
enum class Status {
Available, Unavailable, Losing, Lost
}
}
observeconnectivity class
package com.plcoding.observeconnectivity
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
#OptIn(ExperimentalCoroutinesApi::class)
class NetworkConnectivityObserver(
private val context: Context
): ConnectivityObserver {
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
override fun observe(): Flow<ConnectivityObserver.Status> {
return callbackFlow {
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
launch { send(ConnectivityObserver.Status.Available) }
}
override fun onLosing(network: Network, maxMsToLive: Int) {
super.onLosing(network, maxMsToLive)
launch { send(ConnectivityObserver.Status.Losing) }
}
override fun onLost(network: Network) {
super.onLost(network)
launch { send(ConnectivityObserver.Status.Lost) }
}
override fun onUnavailable() {
super.onUnavailable()
launch { send(ConnectivityObserver.Status.Unavailable) }
}
}
connectivityManager.registerDefaultNetworkCallback(callback)
awaitClose {
connectivityManager.unregisterNetworkCallback(callback)
}
}.distinctUntilChanged()
}
}
how convert MainActivity composble activity to empty activity?
package com.plcoding.observeconnectivity
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.lifecycleScope
import com.plcoding.observeconnectivity.ui.theme.ObserveConnectivityTheme
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class MainActivity : ComponentActivity() {
private lateinit var connectivityObserver: ConnectivityObserver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
connectivityObserver = NetworkConnectivityObserver(applicationContext)
setContent {
ObserveConnectivityTheme {
val status by connectivityObserver.observe().collectAsState(
initial = ConnectivityObserver.Status.Unavailable
)
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "Network status: $status")
}
}
}
}
}
theme.kt
package com.plcoding.observeconnectivity.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
private val DarkColorPalette = darkColors(
primary = Purple200,
primaryVariant = Purple700,
secondary = Teal200
)
private val LightColorPalette = lightColors(
primary = Purple500,
primaryVariant = Purple700,
secondary = Teal200
/* Other default colors to override
background = Color.White,
surface = Color.White,
onPrimary = Color.White,
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
*/
)
#Composable
fun ObserveConnectivityTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: #Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
}
sorry for the inconvenience, but this is my first time trying this. I've been trying on doing this app which it has a recyclerView which I intent it to open a chrome page. The thing is, I'm not able to implement the itemOnClickListener despite having tried a lot of videos. Could you point out where is my mistake?
My Adapter:
package com.example.practica1.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.example.practica1.R
import com.example.practica1.data.Video
class MainAdapter(private val context: Context, private val videos: List<Video>): RecyclerView.Adapter<MainAdapter.ViewHolder>(){
private lateinit var mListener: onItemClickListener
interface onItemClickListener{
fun onItemClick(position: Int)
}
fun setOnClickListener(listener: onItemClickListener){
mListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainAdapter.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_cartoon, parent, false)
return ViewHolder(view, mListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val video = videos[position]
holder.title.text = video.data.short_title
holder.serieTitle.text = video.data.show.title
holder.durationTitle.text = video.data.video.duration + " seg"
Glide.with(context)
.load(video.data.imagepath)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.override(400, 400)
.centerCrop()
.into(holder.imagePrev)
}
override fun getItemCount() = videos.size
inner class ViewHolder(itemView: View, listener: onItemClickListener) : RecyclerView.ViewHolder(itemView){
val title: TextView = itemView.findViewById(R.id.videoTitle)
val serieTitle: TextView = itemView.findViewById(R.id.serieTitle)
val durationTitle: TextView = itemView.findViewById(R.id.durationTitle)
val imagePrev: ImageView = itemView.findViewById(R.id.photoPreview)
init{
itemView.setOnClickListener{
listener.onItemClick(adapterPosition)
}
}
}
}
The fragment in which I tried to implement the method:
package com.example.practica1.fragments
import android.os.Bundle
import android.util.Log
import android.util.Log.d
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import com.example.practica1.R
import com.example.practica1.adapter.MainAdapter
import com.example.practica1.data.ApiInterface
import com.example.practica1.data.Video
import com.example.practica1.databinding.FragmentMenuBinding
import com.haerul.bottomfluxdialog.BottomFluxDialog
import com.haerul.bottomfluxdialog.BottomFluxDialog.OnInputListener
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class MenuFragment : Fragment() {
private var _binding: FragmentMenuBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentMenuBinding.inflate(inflater, container, false)
val view = inflater.inflate(R.layout.fragment_menu, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val retrofit = Retrofit.Builder()
.baseUrl("https://www.cartoonnetwork.com.co")
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(ApiInterface::class.java)
showProgress()
api.fetchAllVideos().enqueue(object : Callback<List<Video>> {
override fun onResponse(call: Call<List<Video>>, response: Response<List<Video>>) {
if (response.isSuccessful()) {
d("daniel", "onResponse ${response.body()!![0].id}")
if (response.body()!!.size > 0) {
showData(response.body()!!)
getResult(true, "Sucess")
} else
getResult(false, response.message())
hideProgress()
}
}
override fun onFailure(call: Call<List<Video>>, t: Throwable) {
d("daniel", "onFailure")
getResult(false, "Request Timeout. Please Try Again!")
hideProgress()
}
})
binding.search.setOnClickListener { v -> showDialog(v) }
}
fun showData(videos: List<Video>){
val test: RecyclerView = requireActivity().findViewById(R.id.recycler_viewMenu)
var adap = MainAdapter(requireContext(), videos)
test.apply{
adapter = adap
}
adap.setOnClickListener(object: MainAdapter.onItemClickListener{
override fun onItemClick(position: Int){
d("test", "test")
}
})
}
fun showProgress(){
binding.swipeRefresh.setRefreshing(true)
binding.emptyView.setVisibility(View.GONE)
binding.shimmer.startShimmer()
binding.shimmer.setVisibility(View.VISIBLE)
binding.recyclerViewMenu.setVisibility(View.GONE)
}
fun hideProgress(){
binding.swipeRefresh.setRefreshing(false)
binding.shimmer.stopShimmer()
binding.shimmer.setVisibility(View.GONE)
binding.recyclerViewMenu.setVisibility(View.VISIBLE)
}
fun getResult(status: Boolean, message: String){
if(!status) {
binding.emptyView.setVisibility(View.VISIBLE)
binding.textEmptyErr.setText(message)
}
else
binding.emptyView.setVisibility((View.GONE))
}
private fun showDialog(view: View){
BottomFluxDialog.inputDialog(requireActivity())
.setTextTitle("Input Title")
.setTextMessage("This is an input message")
.setRightButtonText("SUBMIT")
.setInputListener(object : OnInputListener {
override fun onSubmitInput(text: String?) {
val bundle = Bundle()
bundle.putString("search", text)
Navigation.findNavController(view)
.navigate(R.id.action_menuFragment_to_listFragment, bundle)
}
override fun onCancelInput() {}
})
.show()
}
}
Thanks!
There is no such common thin like OnItemClickListener for RecyclerView, so you have to design your own. It shouldn't be hard to implement by hand, though. What you can do, is that you can Just set OnClickListener to the whole item view and delegate it clicks to upper lambda.
class YourAdapter(
private val listener: (Video) -> Unit,
private val videos: List<Video>,
): ... {
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
...
holder.itemView.setOnClickListener { v ->
if (position != RecyclerView.NO_POSITION) {
listener(videos[position])
}
}
}
}
There are no noticeable side-effects of swapping View.OnClickListener in onBindViewHolder() method.
Also, few suggestions:
Read about ListAdapterhere. Looks like it perfect fit for your usecase.
Use getAbsoluteAdapterPosition() instead of adapterPosition() method within ViewHolder, as adapters are designed to be combined. Read more about ConcatAdapter here.
You don't need to pass Context to adapter constructor. View already has context, so you can query context by calling viewHolder.itemView.context.
Make sure to use your binding. It looks like you creating your binding in MenuFragment, but later you are getting RecyclerView by id in an old-fashioned way.
Don't create adapters, that require data for creation. Instead, pass and update data as you requested from the backend. RecyclerView.Adapter is designed to be reused.
I want to create a nested builder. Something like:
meta('JSON') {
// JSON content using JSON Builder in this case
}
content('HTML') {
// HTML content using MarkupTemplateEngine in this case
}
I don't want to have
JsonBuilder builder = new JsonBuilder()
builder.author {
}
or
def builder = new groovy.xml.MarkupBuilder()
builder.html {
}
How can initial builder and the nested builder be setup?
I have defined the DSL common elements as:
import groovy.transform.CompileStatic
import org.eclipse.collections.impl.map.mutable.UnifiedMap
#CompileStatic
abstract class DSLElement extends UnifiedMap {
DSLElement(Map attributes) {
putAll(attributes)
}
getAt(Object key) {
get(key)
}
putAt(Object key, Object value) {
put(key, value)
}
}
#CompileStatic
class BaseFactoryBuilder extends FactoryBuilderSupport {
BaseFactoryBuilder(boolean init = true) {
super(init)
}
}
#CompileStatic
class DSLFactoryBuilder
extends BaseFactoryBuilder
implements MetaRegistration,
ContentRegistration,
TemplateRegistration {
}
Individual elements I have defined as:
import groovy.transform.CompileStatic
import groovy.transform.InheritConstructors
import groovy.transform.SelfType
#CompileStatic
#InheritConstructors
class Meta extends DSLElement {}
#CompileStatic
class MetaFactory extends AbstractFactory {
boolean isLeaf() {
return false
}
Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes)
throws InstantiationException, IllegalAccessException {
return new Meta(attributes)
}
}
#SelfType(DSLFactoryBuilder)
#CompileStatic
trait MetaRegistration {
registerMeta() {
registerFactory("meta", new MetaFactory())
}
}
and
import groovy.transform.CompileStatic
import groovy.transform.InheritConstructors
import groovy.transform.SelfType
#CompileStatic
#InheritConstructors
class Content extends DSLElement {
}
#CompileStatic
class ContentFactory extends AbstractFactory {
boolean isLeaf() {
return false
}
Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes)
throws InstantiationException, IllegalAccessException {
return new Content(attributes)
}
}
#SelfType(DSLFactoryBuilder)
#CompileStatic
trait ContentRegistration {
registerMeta() {
registerFactory("content", new ContentFactory())
}
}
I have not figured you how to do the nesting another builder and the parameter 'JSON' or 'HTML'.
I am following the FactoryBuilderSupport example given in: Groovy for Domain-Specific Languages
maybe i did not fully understand the question - it's too long to read and code you provided not runnable (or not full)
i assume you want to achieve this:
def html = meta('HTML'){
person(name:'foo')
}
def json = meta('JSON'){
person(name:'bar')
}
if so, here is a code to implement it
String meta(String ctxName, Closure builderBody){
def builder = null
def writer = null
if(ctxName=='HTML'){
writer=new StringWriter()
builder=new groovy.xml.MarkupBuilder(writer)
}else if(ctxName=='JSON'){
builder=new groovy.json.JsonBuilder()
}
else throw new RuntimeException("unsupported meta=`$ctxName`")
def result = builder.with(builderBody)
if(ctxName=='HTML'){
return writer.toString()
}else if(ctxName=='JSON'){
return builder.toPrettyString()
}
}
println meta('HTML'){
person(name:'foo')
}
println meta('JSON'){
person(name:'bar')
}
result:
<person name='foo' />
{
"person": {
"name": "bar"
}
}
I am using cucumber BDD, testng, java to write some BDD test. I would like to mock static classes in order to write my test. However when I write this testrunner, it fails to initialize the BDD scenarios.
Complete Example(note the commented line PrepareForTest) :
import gherkin.events.PickleEvent;
import io.cucumber.testng.CucumberOptions;
import io.cucumber.testng.PickleEventWrapper;
import io.cucumber.testng.TestNGCucumberRunner;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.concurrent.TimeUnit;
import static org.mockito.Matchers.any;
#CucumberOptions(
features = {
"src/test/resources/features/sample"
},
glue = {
"com.demo.stepdefinitions.sample"
},
plugin = {
"pretty",
"html:target/cucumber-reports/cucumber-pretty",
"json:target/cucumber-reports/sampple-report.json",
"rerun:target/cucumber-reports/sample-rerun.txt"
}
)
//#PrepareForTest({Util.class})
public class TestngWithDataproviderTest extends PowerMockTestCase {
private TestNGCucumberRunner testNGCucumberRunner;
private void mockActiveBucket() {
PowerMockito.mockStatic(Util.class);
PowerMockito.when(Util.getBucketId(any(Long.class))).thenReturn(3);
}
#BeforeClass(alwaysRun = true)
public void setUpClass() throws Exception {
testNGCucumberRunner = new TestNGCucumberRunner(this.getClass());
}
#Test(dataProvider = "users")
public void testMockStatic(String username){
System.out.println("username: " + username);
System.out.println("static test passed");
mockActiveBucket();
Assert.assertTrue(true);
}
#Test(groups = "cucumber scenarios", description = "Runs Cucumber Scenarios", dataProvider = "scenarios")
public void testCucumberCcenario(PickleEventWrapper pickleEvent) throws Throwable {
PickleEvent event = pickleEvent.getPickleEvent();
mockActiveBucket();
testNGCucumberRunner.runScenario(pickleEvent.getPickleEvent());
Assert.assertTrue(true);
}
#DataProvider(name = "scenarios")
public Object[][] scenarios() {
Object[][] scenarios = testNGCucumberRunner.provideScenarios();
return new Object[][]{{scenarios[0][0]}};
}
#DataProvider(name = "users")
public Object[][] users() {
return new Object[][]{{"user1"}, {"user2"}};
}
}
class Util {
public static int getBucketId(long eventTimestamp){
Long minsPast5MinBoundary = (eventTimestamp % TimeUnit.MINUTES.toMillis(5))/TimeUnit.MINUTES.toMillis(1);
return minsPast5MinBoundary.intValue();
}
}
The above test fails to load BDD scenarios dataProvider if I enable PrepareForTest annotation on the test. However, the other test which uses dataProvider works fine in both cases(enable or disable PrepareForTest)
ERROR:
Data provider mismatch
Method: testCucumberCcenario([Parameter{index=0, type=io.cucumber.testng.PickleEventWrapper, declaredAnnotations=[]}])
Arguments: [(io.cucumber.testng.PickleEventWrapperImpl) "Sunday isn't Friday"]
at org.testng.internal.reflect.DataProviderMethodMatcher.getConformingArguments(DataProviderMethodMatcher.java:45)
at org.testng.internal.Parameters.injectParameters(Parameters.java:796)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:983)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
at org.testng.TestRunner.privateRun(TestRunner.java:648)
at org.testng.TestRunner.run(TestRunner.java:505)
As a side effect of this, I am unable to mock static methods of util class while writing the BDD. I am new to cucumber BDD. Any help/pointers is appreciated.
After getting some help to root cause from #help-cucumber-jvm slack channel, I was able to root cause it to testng+powermock with dataproviders using custom classes.
For example this test fails
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.ObjectFactory;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import static org.mockito.Matchers.any;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
#PrepareForTest(Util2.class)
public class TestngWithDataproviderTestngTest extends PowerMockTestCase {
#ObjectFactory
public org.testng.IObjectFactory getObjectFactory() {
return new org.powermock.modules.testng.PowerMockObjectFactory();
}
private void mockActiveBucket() {
PowerMockito.mockStatic(Util.class);
PowerMockito.when(Util.getBucketId(any(Long.class))).thenReturn(3);
}
#Test(dataProvider = "users")
public void testMockStatic(MyTestCaseImpl myTestCase) {
System.out.println("myTestCase: " + myTestCase);
System.out.println("static test passed");
mockActiveBucket();
Assert.assertTrue(true);
}
#DataProvider(name = "users")
public Object[][] users() {
return new Object[][]{{new MyTestCaseImpl(5)}};
}
}
//interface MyTestCase {
//}
class MyTestCaseImpl { //implements MyTestCase{
int i;
public MyTestCaseImpl() {
}
public MyTestCaseImpl(int i) {
this.i = i;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
class Util2 {
public static int getBucketId(long eventTimestamp) {
Long minsPast5MinBoundary = (eventTimestamp % TimeUnit.MINUTES.toMillis(5)) / TimeUnit.MINUTES.toMillis(1);
return minsPast5MinBoundary.intValue();
}
}
Here as mentioned, seems to be a known issue with a workaround. Hope this helps.
I am working with android studio and in Kotlin. I am working with a Singleton class so my confusion is that my getInstance function is not being called when I am trying to add that into my setupRecyclerView function and it keeps saying it is an unresolved reference. I also have the issue that my NewsListActivity.SimpleItemRecyclerViewAdapter also keeps getting an unresolved reference when I try to reference it. Any help would be greatly appreciated.
package edu.uw.amjadz.material_news
import android.content.Context
import android.graphics.Bitmap
import android.util.LruCache
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.toolbox.ImageLoader
import com.android.volley.toolbox.Volley
class Singleton private constructor(context: Context){
private var INSTANCE: edu.uw.amjadz.material_news.Singleton? = null
fun getInstance(context: Context): edu.uw.amjadz.material_news.Singleton {
if(INSTANCE == null){
INSTANCE = Singleton(context)
}
return INSTANCE as edu.uw.amjadz.material_news.Singleton
}
val requestQueue: RequestQueue by lazy {
Volley.newRequestQueue(context.applicationContext)
}
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)
}
}
)
}
fun <T> add(req: Request<T>){
requestQueue.add(req)
}
}
private fun setupRecyclerView(recyclerView: RecyclerView) {
recyclerView.setAdapter(null)
val key = getString(R.string.NEWS_API_KEY)
val url = "https://newsapi.org/v2/everything?q=bitcoin&from=2018-10-01&sortBy=publishedAt&apiKey=$key"
val jsonObjectRequest = JsonObjectRequest(
Request.Method.GET, url, null,
Response.Listener { response ->
recyclerView.adapter = NewsListActivity.SimpleItemRecyclerViewAdapter(this, DummyContent.parseData(response.toString()), twoPane)
},
Response.ErrorListener { error ->
}
)
Singleton.getInstance(this).add(jsonObjectRequest)
if (twoPane){
recyclerView.layoutManager = LinearLayoutManager(this)
} else {
recyclerView.layoutManager = GridLayoutManager(this, 2)
}
}
}
class SimpleItemRecyclerViewAdapter(
private val parentActivity: NewsListActivity,
private val values: List<DummyContent.DummyItem>,
private val twoPane: Boolean
) :
RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder>() {
private val onClickListener: View.OnClickListener
init {
onClickListener = View.OnClickListener { v ->
val item = v.tag as DummyContent.DummyItem
if (twoPane) {
val fragment = NewsDetailFragment().apply {
arguments = Bundle().apply {
putString(NewsDetailFragment.ARG_ITEM_ID, item.id)
}
}
parentActivity.supportFragmentManager
.beginTransaction()
.replace(R.id.news_detail_container, fragment)
.commit()
} else {
val intent = Intent(v.context, NewsDetailActivity::class.java).apply {
putExtra(NewsDetailFragment.ARG_ITEM_ID, item.id)
}
v.context.startActivity(intent)
}
}
}