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
}
}
Related
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) {}
}
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;
}
I'm trying to save object in background by using Parse.com but I can't override
override fun done(e: ParseException?) {
//code
}
I'm getting error: Modifier 'override' is not applicable to 'local function
In java I would use:
myObject.saveInBackground(new SaveCallback() {
public void done(ParseException e) {
if (e == null) {
myObjectSavedSuccessfully();
} else {
myObjectSaveDidNotSucceed();
}
}
});
Here is my whole class
class StarterApplication : Application() {
override fun onCreate() {
super.onCreate()
Parse.initialize(Parse.Configuration.Builder(this)
.applicationId(appID)
.clientKey(null)
.server(serverUrl)
.build()
)
var exampleObject: ParseObject = ParseObject("ExampleObject")
exampleObject.put("myString", "fwfwe")
exampleObject.saveInBackground( {
override fun done(e: ParseException?) { //here is an error //`Modifier 'override' is not applicable to 'local function`
}
})
}
}
Just do like that:
exampleObject.saveInBackground(object : SaveCallback {
override fun done(e: ParseException?) {
// Add your code here
}
})
In Java, you declare an anonymous class that extend SaveCallback. In Kotlin, you do this with Object Expressions.
you can also try the simplest solution like this
exampleObject.saveInBackground({
//you code here
})
https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
This question already has answers here:
ClassCastException while using varargs and generics
(3 answers)
Closed 5 years ago.
I'm trying to use something like the visitor pattern, but with return values.
However, although there are no explicit casts, I'm getting a ClassCastException:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.CharSequence;
at Printer.combine(...)
at Split.accept(...)
at MWEKt.main(...)
Code:
interface TreeElem {
fun <T> accept(visitor: TreeVisitor<T>): T
}
class Leaf: TreeElem {
override fun <T> accept(visitor: TreeVisitor<T>): T {
return visitor.visit(this)
}
}
class Split(val left: TreeElem, val right: TreeElem): TreeElem {
override fun <T> accept(visitor: TreeVisitor<T>): T {
return visitor.combine( // this causes cast error
visitor.visit(this),
left.accept(visitor),
right.accept(visitor))
}
}
interface TreeVisitor<T> {
// multiple implementations with different T in future (only one in this example)
fun visit(tree: Leaf): T
fun visit(tree: Split): T
fun combine(vararg inputs: T): T
}
class Printer: TreeVisitor<CharSequence> {
override fun combine(vararg inputs: CharSequence): CharSequence { // error here
return inputs.joinToString(" ")
}
override fun visit(tree: Leaf): CharSequence { return "leaf" }
override fun visit(tree: Split): CharSequence { return "split" }
}
fun main(args: Array<String>) {
val tree = Split(Leaf(), Leaf())
val printer = Printer()
println(tree.accept(printer))
}
I don't know what the problem is. Am I trying to do something impossible, or am I failing to express it correctly, or is type erasure making something that should be possible impossible?
My thoughts so far:
Printer.combine expects CharSequences;
I'm calling a generic overload of TreeElem.accept that returns CharSequence
The compiler probably inserts a cast into the JVM code (type erasure?)
But the runtime types are compatible, so the cast should work
Since the last point is in conflict with realist, I'm probably understanding something incorrectly.
EDIT: I've translated the MWE to Java to see if it's a Kotlin issue and to attract an answer:
interface TreeElem {
<T> T accept(TreeVisitor<T> visitor);
}
class Leaf implements TreeElem {
public <T> T accept(TreeVisitor<T> visitor) {
return visitor.visit(this);
}
}
class Split implements TreeElem {
private TreeElem left;
private TreeElem right;
Split(TreeElem left, TreeElem right) {
this.left = left;
this.right = right;
}
public <T> T accept(TreeVisitor<T> visitor) {
return visitor.combine(
visitor.visit(this),
left.accept(visitor),
right.accept(visitor));
}
}
interface TreeVisitor<T> {
T visit(Leaf tree);
T visit(Split tree);
T combine(T... inputs);
}
class Printer implements TreeVisitor<CharSequence> {
public CharSequence combine(CharSequence... inputs) {
StringBuilder text = new StringBuilder();
for (CharSequence input : inputs) {
text.append(input);
}
return text;
}
public CharSequence visit(Leaf tree) { return "leaf"; }
public CharSequence visit(Split tree) { return "split"; }
}
public class MWEjava {
public static void main(String[] args) {
TreeElem tree = new Split(new Leaf(), new Leaf());
Printer printer = new Printer();
System.out.println(tree.accept(printer));
}
}
The error is the same for the Java case.
I'm pretty sure this is a duplicate of this question: https://stackoverflow.com/a/9058259/4465208
However, to provide a specific solution, you could replace the vararg argument with a List<T> instead, which will work just fine:
class Split(val left: TreeElem, val right: TreeElem) : TreeElem {
override fun <T> accept(visitor: TreeVisitor<T>): T {
return visitor.combine(listOf(
visitor.visit(this),
left.accept(visitor),
right.accept(visitor)))
}
}
interface TreeVisitor<T> {
fun combine(inputs: List<T>): T
// ...
}
class Printer : TreeVisitor<CharSequence> {
override fun combine(inputs: List<CharSequence>): CharSequence {
return inputs.joinToString(" ")
}
// ...
}
Not as pretty, but it plays nice with generics.
I'm writing a library similar to AQuery but with a refined syntax for manipulating elements.
Essentially what AQuery does is safely access the view hierarchy and allow you to call subclass methods on objects like ImageView and TextView.
I've written a generic way to use a subclass of View by using the following code:
My Query object is the base object that's used to manipulate the view hierarchy. The basic format looks like this:
public class Query {
private View mView;
// ...
}
Next is the generic interface. This is an inner interface of the Query object:
private interface Operation<T extends View> {
public void execute(T view);
}
Next is the run method in Query. This checks the current node this query represents and calls the execute method on the Operation object if it is successful:
private <T extends View> Query run(Class<T> cls, Operation<T> operation) {
T t = cls.cast(mView);
if (t == null) {
Log.e(TAG, "view is not a " + cls.getSimpleName());
} else {
operation.execute(t);
}
return this;
}
So now that the template code is written, I use methods similar to this to implement functionality:
public Query text(final CharSequence str) {
return run(TextView.class, new Operation<TextView>() {
#Override
public void execute(TextView view) {
view.setText(str);
}
});
}
For every method that modifies the View hierarchy, I have to write this boilerplate-looking code.
Is there any way I can refactor this code to make methods like text simpler?
FYI what you have here isn't really checking the type of mView. Class.cast will throw a ClassCastException if mView is not assignable to type T, so the log message there doesn't actually represent what happens. t == null would be true if and only if mView were null.
It's a little hard to tell what you're trying to achieve without some stubs of what Query will do. If your use would allow parameterization of Query, then you can just make the operation a function of that. This would give you compile-time checks of the view matching the type of the query. e.g.
public interface Query<ViewT extends View> {
void run(ViewT view);
}
public Query<TextView> text(final CharSequence str) {
return new Query<TextView>() {
public void run(TextView view) {
view.setText(str);
}
};
}
If that's not possible (i.e. the view types are never known at compile time) then you can still parameterize the implementation of it and simply perform the action if and only if the argument type matches the query type. e.g.:
public interface Query {
void run(View view);
}
private abstract class TypedQuery<ViewT extends View> implements Query {
private final Class<ViewT> viewType;
private TypedQuery(Class<ViewT> viewType) {
this.viewType = viewType;
}
public final void run(View view) {
if (viewType.isInstance(view)) {
runInternal((ViewT) view);
} else {
Log.e(TAG, "view " + view + " is not a " + viewType.getSimpleName());
}
}
protected abstract void runInternal(ViewT view);
}
public Query text(final CharSequence str) {
return new TypedQuery<TextView>(TextView.class) {
#Override
protected void runInternal(TextView view) {
view.setText(str);
}
};
}