What is the Kotlin equivalent of this Java class - java

I am trying to convert the following Java class in Kotlin
abstract class BaseExpandedViewCreator implements NotificationViewCreator
{
protected RawNotification rawNotification;
protected final Context context;
BaseExpandedViewCreator(#NonNull Context context)
{
this.context = Objects.requireNonNull(context);
}
#Override
public void setRawNotification(#NonNull RawNotification rawNotification)
{
this.rawNotification = rawNotification;
initRawNotification(rawNotification);
}
/**
* Override this function to initialise {#link RawNotification} for view creators if needed.
*/
protected void initRawNotification(#NonNull RawNotification rawNotification) {}
}
Kotlin interface
interface NotificationViewCreator {
fun setRawNotification(rawNotification: RawNotification)
}
This is my implementation:
abstract class BaseExpandedViewCreator(
protected val context: Context
):NotificationViewCreator {
var rawNotification: RawNotification ? = null
fun setRawNotification(rawNotification: RawNotification) {
this.rawNotification = rawNotification
initRawNotification(rawNotification)
}
fun initRawNotification(rawNotification: RawNotification) {}
}
I get the following error
Platform declaration clash: The following declarations have the same JVM signature (setRawNotification(Lcom/myproject/RawNotification;)V):
public final fun <set-rawNotification>(<set-?>: RawNotification): Unit defined in com.myproject.BaseExpandedViewCreator
public final fun setRawNotification(rawNotification: RawNotification): Unit defined in com.myproject.BaseExpandedViewCreator

You can change visibility of var rawNotification to private to avoid property/setter name clash:
abstract class BaseExpandedViewCreator(
private val context: Context
): NotificationViewCreator {
private lateinit var rawNotification: RawNotification // if you want non-nullable property
// OR
private var rawNotification: RawNotification? = null // if you are OK with nullable property
override fun setRawNotification(rawNotification: RawNotification) {
this.rawNotification = rawNotification
initRawNotification(rawNotification)
}
fun initRawNotification(rawNotification: RawNotification) {}
}

Related

java.lang.ClassCastException: com.myapp.MainActivity cannot be cast to com.facebook.react.bridge.ReactContext

I am trying to create a native ui with fragment on android
CealScanQrView.kt
class CealScanQrView(context: Context): FrameLayout(context) {
...
//Contains all the logic of integrating camerax, check below code repo to see the full source code
...
//Now while submitting the data from native side to react-native I get error
val reactContext = context as ReactContext ///This line causes error and makes my app crash
reactContext
.getJSModule(RCTEventEmitter::class.java)
.receiveEvent(id, topChange, event)
}
CealScanQrFragment.kt
class CealScanQrFragment: Fragment() {
private lateinit var cealScanQrView: CealScanQrView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
cealScanQrView = CealScanQrView(requireNotNull(context))
return cealScanQrView // this CustomView could be any view that you want to render
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// do any logic that should happen in an `onCreate` method, e.g:
cealScanQrView.setUpCamera(requireActivity())
}
override fun onDestroy() {
super.onDestroy()
cealScanQrView.destroyCamera()
}
}
CealScanQrViewManager.kt
class CealScanQrViewManager(
private val reactContext: ReactApplicationContext
) : ViewGroupManager<FrameLayout>() {
private val cealScanQrView = "CealScanQrView"
private val topChange = "topChange"
private val phasedRegistrationNames = "phasedRegistrationNames"
private val bubbled = "bubbled"
private val onChange = "onChange"
private val create = "create"
companion object {
private const val COMMAND_CREATE = 1
}
private var propWidth: Int? = null
private var propHeight: Int? = null
override fun getName() = cealScanQrView
override fun createViewInstance(reactContext: ThemedReactContext) = FrameLayout(reactContext)
override fun getCommandsMap() = mapOf("create" to COMMAND_CREATE)
override fun receiveCommand(root: FrameLayout, commandId: String?, args: ReadableArray?) {
super.receiveCommand(root, commandId, args)
val reactNativeViewId = requireNotNull(args).getInt(0)
when (commandId?.toInt()) {
COMMAND_CREATE -> createFragment(root, reactNativeViewId)
}
}
private fun createFragment(root: FrameLayout, reactNativeViewId: Int) {
val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
setupLayout(parentView)
val myFragment = CealScanQrFragment()
val activity = reactContext.currentActivity as FragmentActivity
activity.supportFragmentManager
.beginTransaction()
.replace(reactNativeViewId, myFragment, reactNativeViewId.toString())
.commit()
}
private fun setupLayout(view: View) {
Choreographer.getInstance().postFrameCallback(object: Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
manuallyLayoutChildren(view)
view.viewTreeObserver.dispatchOnGlobalLayout()
Choreographer.getInstance().postFrameCallback(this)
}
})
}
#ReactPropGroup(names = ["width", "height"], customType = "Style")
fun setStyle(view: FrameLayout, index: Int, value: Int) {
if (index == 0) propWidth = value
if (index == 1) propHeight = value
}
private fun manuallyLayoutChildren(view: View) {
// propWidth and propHeight coming from react-native props
val width = requireNotNull(propWidth)
val height = requireNotNull(propHeight)
view.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
view.layout(0, 0, width, height)
}
override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
return mapOf(
topChange to mapOf(
phasedRegistrationNames to mapOf(
bubbled to onChange
)
)
)
}
}
val reactContext = context as ReactContext causes the app to crash and throw error saying
java.lang.ClassCastException: com.myapp.MainActivity cannot be cast to com.facebook.react.bridge.ReactContext
What kind of context I should pass from my fragment to Native UI View so I can use getJSModule method to send data
Full source code here
You can not cast android's Context class to the ReactContext.
If you will use reactContext inside your View class, then you can pass ReactContext instance from ViewManager --> Fragment --> View
1 - Write interface like this:
interface ReactContextProvider {
fun provideReactContext(): ReactApplicationContext
}
2 - Implement this interface in your CealScanQrViewManager class:
class CealScanQrViewManager(
private val reactContext: ReactApplicationContext
) : ViewGroupManager<FrameLayout>(), ReactContextProvider {
...
override fun provideReactContext(): ReactApplicationContext {
return reactContext
}
...
}
3 - Add reactContextProvider to the CealScanQrFragment class
var reactContextProvider: ReactContextProvider? = null
4 - Pass this interface to the CealScanQrFragment class in your createFragment function on CealScanQrViewManager class.
val myFragment = CealScanQrFragment()
myFragment.reactContextProvider = this
5 - Define a variable in your CealScanQrView class:
var reactContextProvider: ReactContextProvider? = null
6 - Pass this variable to the CealScanQrView instance on your onCreateView function on CealScanQrFragment:
cealScanQrView = CealScanQrView(requireNotNull(context))
cealScanQrView.reactContextProvider = reactContextProvider
7 - Use your reactContextProvider inside View like below:
reactContextProvider?.provideReactContext()?.let { reactContext -->
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, topChange, event)
}

How to intercept Kotlin Coroutines?

I'm trying to instrument Kotlin coroutines, similar to what's done here using a Javaagent. I don't want a Javaagent.
The first step is to intercept the creation, suspension and resumption of Coroutines defined in the DebugProbes. The code for that is as follows:
public class Instrumentor {
private static final Logger LOG = LoggerFactory.getLogger(Instrumentor.class);
public static void install() {
TypeDescription typeDescription = TypePool.Default.ofSystemLoader()
.describe("kotlin.coroutines.jvm.internal.DebugProbesKt")
.resolve();
new ByteBuddy()
.redefine(typeDescription, ClassFileLocator.ForClassLoader.ofSystemLoader())
.method(ElementMatchers.named("probeCoroutineCreated").and(ElementMatchers.takesArguments(1)))
.intercept(MethodDelegation.to(CoroutineCreatedAdvice.class))
.method(ElementMatchers.named("probeCoroutineResumed").and(ElementMatchers.takesArguments(1)))
.intercept(MethodDelegation.to(CoroutineResumedAdvice.class))
.method(ElementMatchers.named("probeCoroutineSuspended").and(ElementMatchers.takesArguments(1)))
.intercept(MethodDelegation.to(CoroutineSuspendedAdvice.class))
.make()
.load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION);
DebugProbes.INSTANCE.install();
}
public static void uninstall() {
DebugProbes.INSTANCE.uninstall();
}
public static class CoroutineCreatedAdvice {
#Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static Continuation<Object> exit(#Advice.Return(readOnly = false) Continuation<Object> retVal) {
LOG.info("Coroutine created: {}", retVal);
return retVal;
}
}
public static class CoroutineResumedAdvice {
#Advice.OnMethodEnter(suppress = Throwable.class)
public static void enter(#Advice.Argument(0) final Continuation<Object> continuation) {
LOG.info("Coroutine resumed: {}", continuation);
}
}
public static class CoroutineSuspendedAdvice {
#Advice.OnMethodEnter(suppress = Throwable.class)
public static void enter(#Advice.Argument(0) final Continuation<Object> continuation) {
LOG.info("Coroutine suspended: {}", continuation);
}
}
}
JUnit5 test to trigger interception:
class CoroutineInstrumentationTest {
companion object {
#JvmStatic
#BeforeAll
fun beforeAll() {
Instrumentor.install()
}
#JvmStatic
#AfterAll
fun afterAll() {
Instrumentor.uninstall()
}
}
#Test
fun testInterception() {
runBlocking {
println("Test")
}
}
}
However, no interception happens (confirmed by the absence of log statements and by using a debugger). I'm new to Byte Buddy, so it's possible I'm missing something. Any ideas?
Kotlin v1.4.10, Kotlin Coroutines v1.3.9, Byte Buddy v1.10.17.
Are you sure the class is not yet loaded at this point? Try setting a breakpoint in ClassInjector.UsingReflection to see if you acutally walk through or of the injection is aborted due to a previously loaded class.
The cleaner solution would be a Java agent. You can use byte-buddy-agent to create one dynamically by ByteBuddyAgent.install() and then register an AgentBuilder on it.

Android lint custom check UCallExpression type of method receiver

I'm writing custom lint check to ban some methods. So if someone calls banned method foo on instance of class A, lint should report error.
I achieved this for static methods like this (inside visitCallExpression(UCallExpression):
node.receiver as UReferenceExpression).getQualifiedName()
From qualified name I can get the Class object and run my check but I can't get the qualified name for methods called on instantiated object. I can get the name of the class to which the objects belongs but not the qualified name.
How do I get qualified name of class of method that is called on instance of that class? If I'm being unclear, here is an example.
import android.view.Button;
class ButtonSetTextIntClass {
private Button button;
public void bannedSetText (){
button.setText(123);
}
}
And I need in visitCallExpression (UCallExpression) get qualified name/class of button.
UCallExpression.receiverType does what you want:
public class CustomDetector extends Detector implements SourceCodeScanner {
#Nullable
#Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.singletonList(UCallExpression.class);
}
#Nullable
#Override
public UElementHandler createUastHandler(#NotNull JavaContext context) {
return new UElementHandler() {
#Override
public void visitCallExpression(#NotNull UCallExpression node) {
node.getReceiverType(); // PsiType:Button
}
};
}
}
To extract qualified name you can use the following method:
((PsiClassType) node.getReceiverType()).resolve().getQualifiedName() // android.widget.Button
I found a solution which works for both static and non-static methods and for Kotlin top level functions. Not sure if it is the best way to do it but at least it works.
override fun visitCallExpression(node: UCallExpression) {
(node.resolve()?.parent as? ClsClassImpl)?.stub?.qualifiedName
}
/**
* eg: android.util.Log
* ps. imports was initialized in visitImportStatement
*/
private fun getClassNameWithPackage(node: UCallExpression): String {
var className = node.resolve()?.containingClass?.qualifiedName
if (className != null) {
return className
}
className = getClassName(node) ?: return ""
for (import in imports) {
if (import.contains(className)) {
return import
}
}
return "$packageName.$className"
}
/**
* eg: Log
*/
private fun getClassName(node: UCallExpression): String? {
return node.receiver?.javaPsi?.text ?: when (val uExpression = (node.methodIdentifier?.uastParent as UCallExpression?)?.receiver) {
is JavaUSimpleNameReferenceExpression -> {
uExpression.identifier
}
is KotlinUSimpleReferenceExpression -> {
uExpression.identifier
}
is UReferenceExpression -> {
uExpression.getQualifiedName()
}
else -> null
}
}

How to properly reload liveData manually in Android?

My app is a basic news app which fetches data from JSON provided by Guardian API.
I parsed the values from JSON using raw java code (not using retrofit).
Then I get the LiveData in NewsFeedViewModel class which extends as AndroidViewModel.
And then in the fragment, I submit list to adapter.
These are the issues I'm facing:
1) at first, if the articles to show is set to 10, then if i go to settings and change it to 2, then the last 8 articles are disappearing but the white space /gap is not going. I can still scroll through the empty gap.
2) if i change the number of articles value constantly, then app is becoming un-scrollable.
And i have a few more doubts, how to refresh the data manually when swipeToRefresh is happened?
This is my project github link: https://github.com/sdzshn3/News24-7-RV
Video sample of the issue happening in app: https://drive.google.com/file/d/1gr_fabS2rqREuyecvGSG3IQ_jXOowlW7/view?usp=drivesdk
In kotlin style:
class RefreshableLiveData<T>(
private val source: () -> LiveData<T>
) : MediatorLiveData<T>() {
private var liveData = source()
init {
this.addSource(liveData, ::observer)
}
private fun observer(data: T) {
value = data
}
fun refresh() {
this.removeSource(liveData)
liveData = source()
this.addSource(liveData, ::observer)
}
}
Example:
class MainActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.goals.observe(this) { result ->
// update UI
}
// refresh
viewModel.refresh()
}
}
class MyViewModel(useCase: MyUseCase): ViewModel() {
private val _goals = RefreshableLiveData {
useCase.getGoals()
}
val goals: LiveData<Result<List<GoalItem>>>
get() = _goals.map(GoalItem::fromEntity)
fun refresh() {
_goals.refresh()
}
}
class MyUseCase {...}
...
You need to do exactly what I did in this Reddit post:
public class RefreshLiveData<T> extends MutableLiveData<T> {
public interface RefreshAction<T> {
private interface Callback<T> {
void onDataLoaded(T t);
}
void loadData(Callback<T> callback);
}
private final RefreshAction<T> refreshAction;
private final Callback<T> callback = new RefreshAction.Callback<T>() {
#Override
public void onDataLoaded(T t) {
postValue(t);
}
};
public RefreshLiveData(RefreshAction<T> refreshAction) {
this.refreshAction = refreshAction;
}
public final void refresh() {
refreshAction.loadData(callback);
}
}
Then you can do
public class YourViewModel extends ViewModel {
private final GithubRepository githubRepository;
public YourViewModel(GithubRepository githubRepository, SavedStateHandle savedStateHandle) {
this.githubRepository = githubRepository;
}
private final LiveData<String> userId = savedStateHandle.getLiveData("userId"); // from args
private final RefreshLiveData<List<Project>> refreshLiveData = Transformations.switchMap(userId, (uId) -> {
return githubRepository.getProjectList(uId);
});
public void refreshData() {
refreshLiveData.refresh();
}
public LiveData<List<Project>> getProjects() {
return refreshLiveData;
}
}
And then repository can do:
public RefreshLiveData<List<Project>> getProjectList(String userId) {
final RefreshLiveData<List<Project>> liveData = new RefreshLiveData<>((callback) -> {
githubService.getProjectList(userId).enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
callback.onDataLoaded(response.body());
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
}
});
});
return liveData;
}

Java vs Kotlin generics

I have the following Java class I'm trying to convert to Kotlin that uses generics.
abstract class MvpViewHolder, M, V : View?>(itemView: View) : RecyclerView.ViewHolder(itemView) {
public abstract class MvpViewHolder<P extends BasePresenter> extends RecyclerView.ViewHolder {
protected P presenter;
public MvpViewHolder(View itemView) {
super(itemView);
}
public void bindPresenter(P presenter) {
this.presenter = presenter;
presenter.bindView(this);
}
public void unbindPresenter() {
presenter = null;
}
}
Here is my Kotlin attempt
abstract class MvpViewHolder<P : BasePresenter>(itemView: View) : RecyclerView.ViewHolder(itemView) {
protected var presenter: P? = null
fun bindPresenter(presenter: P?): Unit {
this.presenter = presenter
presenter?.bindView(this)
}
fun unbindPresenter(): Unit {
this.presenter = null
}
}
I'm particularly running into a problem with the generics. It turns out that in Kotlin it's simply not enough to do MvpViewHolder<P : BasePresenter> as Kotlin requires that we pass in the 2 type arguments for BasePresenter (whose implementation I put below)
However, if I need to pass in the type arguments for BasePresenter then my method signature would then look like this
`abstract class MvpViewHolder<P : BasePresenter<*, *>>(itemView: View) : RecyclerView.ViewHolder(itemView) {`
This does not help me however, because in presenter.bindView(this) I get a type error of Required: Nothing, Found: MvpViewHolder
I could also get more specific and pass in
MvpViewHolder<P: BasePresenter<M, V>, M, V> but then that would mean that wherever I call MvpViewHolder, I then also have to include 2 extra type parameters. Not only will that be tedious to deal with now having to maintain, but it just makes me sad.
How can I either get rid of the error when I use BasePresenter<,> or avoid having to pass in 3 type parameters into my MvpViewHolder class, just so I can define P as a BasePresenter
abstract class BasePresenter<M, V> {
var model: M? = null
var view: WeakReference<V>? = null
fun setM(model: M?): Unit {
this.model = model
if (setupDone()) {
updateView()
}
}
fun bindView(view: V) {
this.view = WeakReference(view)
}
fun unbindView() {
this.view = null
}
abstract fun updateView()
fun view(): V? {
return if (view == null) null else view?.get()
}
fun setupDone(): Boolean {
return view() != null && model != null
}
}
Change abstract class to the following code
abstract class MvpViewHolder<P :BasePresenter<P,MvpViewHolder<P>>>(itemView: View) : RecyclerView.ViewHolder(itemView) {
protected var presenter: P? = null
fun bindPresenter(presenter: P) {
this.presenter = presenter
presenter.bindView(this)
}
fun unbindPresenter() {
presenter = null
}
}

Categories

Resources