Categories
discuss

Android Dialog Fragment RecyclerView wrap content but max height based on constraints?

So I’m working on creating a dialog fragment to allow user to choose from some options. I have a pretty simple layout inside a constraint layout. TextView on top, recycler view, then two buttons at the bottom.

The problem is, I want the recyclerview to be wrap content, so that if there aren’t a lot of options, the dialog will shrink down. However, if there are a lot of options, i’d like it to expand but then start scrolling so all views are visible on the screen.

I can’t seem to get past the situation where either it constantly is large. Or if I just allow wrap content, the dialog will grow so large the bottom buttons are missing.

I’m assuming it has something to do with some particular constraint options, but I can’t figure out the combination. Any ideas?

EDIT: I know an easy answer is to set a max height on the recycler view. I’m hoping to do that same thing but with constraints, so its not a fixed hard height.

EDIT2: It looks like the constraints will work nicely with wrap as default if the view model’s height is fixed. I really can’t deal with a fixed height view model though…

Thanks

Answer

Create a customRecyclerView that override onMeasure method.

public class CustomRecyclerView extends RecyclerView{

    public CustomRecyclerView (Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomRecyclerView (Context context) {
        super(context);
    }

    public CustomRecyclerView (Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
                MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }

}

you can call the recyclerview like this

<com.example.yourpackage.CustomRecyclerView>
Categories
discuss

How to turn off string concatenation optimization

In Java 9 Oracle improved String concatenation. Now "" + someBoolean turns into invokedynamic with StringConcatFabric.makeConcat as bootstrap method. That fabric generates classes at runtime that concatenate your strings. I want to disable this behaviour and fallback to plain old string builder.
So I supposed that javac has flag that do what I want. But I can’t find it.

Answer

There are two parts to the string concatenation feature.

  1. At runtime

    In Java 9+, at runtime, String concatenation is controlled by the StringConcatFactory class (javadoc). That’s because javac generates invokedynamic bytecode to StringConcatFactory::makeConcat wherever String concatenation is needed.

    StringConcatFactory defines several strategies for runtime concatenation in the form of a Strategy enum (source code).

    You can change the default strategy from the command line by setting -Djava.lang.invoke.stringConcat

    To get the Java-8 behavior at runtime, you need to set it to BC_SB, which stands for “Bytecode, StringBuilder”

    Here are the other values, for completeness:

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder}.
     */
    BC_SB,
    
    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but trying to estimate the required storage.
     */
    BC_SB_SIZED,
    
    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but computing the required storage exactly.
     */
    BC_SB_SIZED_EXACT,
    
    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also tries to estimate the required storage.
     */
    MH_SB_SIZED,
    
    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also estimate the required storage exactly.
     */
    MH_SB_SIZED_EXACT,
    
    /**
     * MethodHandle-based generator, that constructs its own byte[] array from
     * the arguments. It computes the required storage exactly.
     */
    MH_INLINE_SIZED_EXACT
    
  2. At compile time

    As Kayaman correctly notes, the StringConcatFactory affects the program at runtime only. The bytecode will still contain an invokedynamic to StringConcatFactory wherever Strings are concatenated. There are several ways of getting back the calls to StringBuilder:

    • The most straightforward approach of disabling this behavior is to pass the --release=8 flag to javac to force the generation of Java-8 compatible code. However, this affects not only string concatenation.

    • A more targeted option is to control concatenation specifically, by passing -XDstringConcat=inline.

      Let’s take this piece of code as an example:

      public class Print {    
          public static void main(String[] args) {
              String foo = "a";
              String bar = "b";
              System.out.println(foo+bar);
          }
      }
      

      If we compile it without any flags, we’ll get:

      public class Print {
        public Print();
          Code:
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
      
        public static void main(java.lang.String[]);
          Code:
             0: ldc           #2                  // String a
             2: astore_1
             3: ldc           #3                  // String b
             5: astore_2
             6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             9: aload_1
            10: aload_2
            11: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
            16: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            19: return
      }
      

      Note the invokedynamic to makeConcatWithConstants.

      However, if we run javac -XDstringConcat=inline Print.java, we’ll get this:

      public class Print {
        public Print();
          Code:
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
      
        public static void main(java.lang.String[]);
          Code:
             0: ldc           #2                  // String a
             2: astore_1
             3: ldc           #3                  // String b
             5: astore_2
             6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             9: new           #5                  // class java/lang/StringBuilder
            12: dup
            13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
            16: aload_1
            17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            20: aload_2
            21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            30: return
      }
      

      Here the String concatenation is done using the StringBuilder, just like in Java 8.

Categories
discuss

Navigation drawer item click listener not working

Sorry for the silly question i am amateur in android studio and learning now. I have tried a lot but the click listener is not working please help. i have used the android studio’s default drawer layout.navigation is working but i want to perform a special action such as using a new intent to open another app.I am trying to use it on the ID nav_link to perform a simple toast but its not working.

package com.demo.navdraw;
import android.content.ClipData;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import android.widget.Toast;



import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {

private AppBarConfiguration mAppBarConfiguration;
private MenuItem item;

@Override
public boolean onCreateOptionsMenu(Menu menu) {

    getMenuInflater().inflate(R.menu.activity_main_drawer, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();


    if (id == R.id.nav_link) {
        Toast.makeText(this, "Setting", Toast.LENGTH_LONG).show();
        return true;
    }

    return super.onOptionsItemSelected(item);
}


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar=findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    FloatingActionButton fab=findViewById(R.id.fab);



    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();
        }
    });



    DrawerLayout drawer=findViewById(R.id.drawer_layout);
    NavigationView navigationView=findViewById(R.id.nav_view);
    navigationView.setNavigationItemSelectedListener(this);
    // Passing each menu ID as a set of Ids because each
    // menu should be considered as top level destinations.
    mAppBarConfiguration=new AppBarConfiguration.Builder(
            R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow,
            R.id.nav_tools, R.id.nav_share, R.id.nav_send, R.id.nav_profile, R.id.nav_link)
            .setDrawerLayout(drawer)
            .build();
    NavController navController=Navigation.findNavController(this, R.id.nav_host_fragment);
    NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
    NavigationUI.setupWithNavController(navigationView, navController);


}

@Override
public boolean onSupportNavigateUp() {
    NavController navController=Navigation.findNavController(this, R.id.nav_host_fragment);
    return NavigationUI.navigateUp(navController, mAppBarConfiguration)
            || super.onSupportNavigateUp();
}


@Override
public boolean onNavigationItemSelected(@NonNull MenuItem Item) {
    int id=item.getItemId();

    if (id==R.id.nav_link){

            Toast.makeText(getApplicationContext(), "Link", Toast.LENGTH_LONG).show();
            return true;

        }
    return true;
    }


}

Answer

You could follow two approach for this.
First approach
would be to use the setOnMenuItemClickListener when you want to implement listener only for a single item in the navigation drawer. This adds a listener for a single item in the navigation drawer without affecting the other navigation items.

 val navigationView: NavigationView = findViewById(R.id.nav_view) as NavigationView

    navigationView.menu!!.findItem(R.id.nav_logout).setOnMenuItemClickListener { menuItem:MenuItem? ->
        //write your implementation here
        //to close the navigation drawer
        if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
            drawer_layout.closeDrawer(GravityCompat.START)
        }
        Toast.makeText(applicationContext, "single item click listener implemented", Toast.LENGTH_SHORT).show()
        true
    }



Second Approach
would be to use the setNavigationItemSelectedListener when you want to write listener for each item in the navigation drawer.

val navigationView: NavigationView = findViewById(R.id.nav_view) as NavigationView
 navigationView.setNavigationItemSelectedListener { menuItem ->
    when (menuItem.itemId) {
        R.id.nav_gallery -> {
            //write your implementation here
            if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
                drawer_layout.closeDrawer(GravityCompat.START)
            }
            true
        }
        else -> {
            if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
                drawer_layout.closeDrawer(GravityCompat.START)
            }
            false
        }
    }
}
Categories
discuss

Moshi 1.9.x Cannot serialize Kotlin type

I’m running into the following crash and stack trace after upgrading to Moshi 1.9.1 (from 1.8.0):

java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.garpr.android.data.models.RankedPlayer. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin-codegen artifact.
for class com.garpr.android.data.models.RankedPlayer
for class com.garpr.android.data.models.AbsPlayer

    at com.squareup.moshi.Moshi$LookupChain.exceptionWithLookupStack(Moshi.java:349)
    at com.squareup.moshi.Moshi.adapter(Moshi.java:150)
    at com.squareup.moshi.Moshi.adapter(Moshi.java:98)
    at com.squareup.moshi.AdapterMethodsFactory$AdapterMethod.bind(AdapterMethodsFactory.java:313)
    at com.squareup.moshi.AdapterMethodsFactory.create(AdapterMethodsFactory.java:62)
    at com.squareup.moshi.Moshi.adapter(Moshi.java:138)
    at com.squareup.moshi.Moshi.adapter(Moshi.java:98)
    at com.squareup.moshi.Moshi.adapter(Moshi.java:72)
    at com.garpr.android.data.converters.AbsPlayerConverterTest.setUp(AbsPlayerConverterTest.kt:76)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:546)
    at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:252)
    at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.garpr.android.data.models.RankedPlayer. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin-codegen artifact.
    at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:83)
    at com.squareup.moshi.Moshi.nextAdapter(Moshi.java:169)
    at com.squareup.moshi.AdapterMethodsFactory$AdapterMethod.bind(AdapterMethodsFactory.java:312)
    at com.squareup.moshi.AdapterMethodsFactory.create(AdapterMethodsFactory.java:62)
    at com.squareup.moshi.Moshi.adapter(Moshi.java:138)
    ... 23 more

I saw this other Stack Overflow answer, but it does not apply to me, as I’ve already added the relevant @JsonClass(generateAdapter = X) line to my classes.

I use a custom AbsPlayerConverter class for my AbsPlayer class, this is used so that I can determine which subclass to resolve to. And then, if it resolves to RankedPlayer, I use another custom converter for that (RankedPlayerConverter).

Here is my Moshi-builder code:

Moshi.Builder()
        .add(AbsPlayerConverter)
        .add(AbsRegionConverter)
        .add(AbsTournamentConverter)
        .add(MatchConverter)
        .add(RankedPlayerConverter)
        .add(SimpleDateConverter)
        .build()

And here is Moshi in my gradle file:

implementation "com.squareup.moshi:moshi:1.9.1"
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.9.1"

So finally, after all of that text and info, I have no idea how I can be running into this crash. I clearly have defined how to serialize/deserialize my RankedPlayer class. And if I downgrade to Moshi 1.8.0, and leave my codebase completely as-is, this crash goes away and everything works flawlessly.

Anyone have any ideas?

Thanks!!

Answer

The error message specifically says Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin-codegen artifact

As per the Kotlin part of the readme, you must add the KotlinJsonAdapterFactory if you’re not using Moshi’s codegen. This was a specific behavior change in Moshi 1.9 as per the blog post about Moshi 1.9.

Moshi.Builder()
    .add(AbsPlayerConverter)
    .add(AbsRegionConverter)
    .add(AbsTournamentConverter)
    .add(MatchConverter)
    .add(RankedPlayerConverter)
    .add(SimpleDateConverter)
    .add(KotlinJsonAdapterFactory())
    .build()

And make sure you’re using implementation("com.squareup.moshi:moshi-kotlin:1.9.1")

Categories
discuss

How do lambda calls interact with Interfaces?

The code snippet shown below works. However, I’m not sure why it works. I’m not quite following the logic of how the lambda function is passing information to the interface.

Where is control being passed? How is the compiler making sense of each n in the loop and each message created?

This code compiles and gives the expected results. I’m just not sure how.

import java.util.ArrayList;
import java.util.List;

public class TesterClass {

    public static void main(String[] args) {

        List<String> names = new ArrayList<>();

        names.add("Akira");
        names.add("Jacky");
        names.add("Sarah");
        names.add("Wolf");

        names.forEach((n) -> {
            SayHello hello = (message) -> System.out.println("Hello " + message);
            hello.speak(n);
        });
    }

    interface SayHello {
        void speak(String message);
    }
}

Answer

The SayHello is a Single Abstract Method interface which has a method that takes a string and returns void. This is analogous to a consumer. You are just providing an implementation of that method in form of a consumer which is similar to the following anonymous inner class implementation.

SayHello sh = new SayHello() {
    @Override
    public void speak(String message) {
        System.out.println("Hello " + message);
    }
};

names.forEach(n -> sh.speak(n));

Fortunately, your interface has a single method such that the type system (more formally, the type resolution algorithm) can infer its type as SayHello. But if it were to have 2 or more methods, this would not be possible.

However, a much better approach is to declare the consumer before the for-loop and use it as shown below. Declaring the implementation for each iteration creates more objects than necessary and seems counter-intuitive to me. Here’s the enhanced version using method references instead of lambda. The bounded method reference used here calls the relevant method on the hello instance declared above.

SayHello hello = message -> System.out.println("Hello " + message);
names.forEach(hello::speak);

Update

Given that for stateless lambdas that does not capture anything from their lexical scope only once instance will ever be created, both of the approaches merely create one instance of the SayHello, and there’s no any gain following the suggested approach. However this seems to be an implementation detail and I didn’t know it until now. So a much better approach is just to pass in a consumer to your forEach as suggested in the comment below. Also note that all these approaches creates just one instance of your SayHello interface while the last one is more succinct. Here’s how it looks.

names.forEach(message -> System.out.println("Hello " + message));

This answer will give you more insight on that. Here’s the relevant section from the JLS §15.27.4: Run-Time Evaluation of Lambda Expressions

These rules are meant to offer flexibility to implementations of the Java programming language, in that:

  • A new object need not be allocated on every evaluation.

In fact, I initially thought every evaluation creates a new instance, which is wrong. @Holger thanks for pointing that out, good catch.

Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..