Categories
discuss

Can IntelliJ convert my old string concatenation to new Text Block feature previewed in Java 14?

I have existing code such as this:

String sql = "CREATE TABLE " + tableName + " (n" +
        "  id_ UUID DEFAULT random_uuid() PRIMARY KEY ,n" +
        "  when_ TIMESTAMP WITHOUT TIME ZONE NOT NULLn" +
        "  duration_ STRING NOT NULLn" +
        ");";

…in a project in IntelliJ 2020.1.1 (preview) Ultimate edition where the JDK settings are configured for language level 14 (Preview) - Records, patters, text blocks.

So I expected IntelliJ to offer a conversion to text blocks among the items display when I click on the yellow light bulb that appears next to this line of code. But I see no such offer.

screen shot of pop-up menu displayed by clicking on yellow light bulb next to this line of code

I am surprised to not find such an offer, as the JetBrains company claims to support JEP 368: Text Blocks (Second Preview) in their 2020.1 version of IntelliJ, as discussed here and here.

βž₯ Does IntelliJ offer some way to convert old string concatenation to text blocks?

Answer

Disclosure: IntelliJ IDEA developer is here.


Currently, IntelliJ IDEA only suggests converting the complete literal concatenation into the text block. Here, however, you have a concatenation that involves the tableName variable. This prevents inspection from starting. You can work-around this adding bogus parentheses:

String sql = "CREATE TABLE " + tableName + (" (n" +
             "  id_ UUID DEFAULT random_uuid() PRIMARY KEY ,n" +
             "  when_ TIMESTAMP WITHOUT TIME ZONE NOT NULLn" +
             "  duration_ STRING NOT NULLn" +
             ");");

After that, the conversion is suggested on the first n character:

IntelliJ IDEA screenshot

The result after removing parentheses looks like this:

String sql = "CREATE TABLE " + tableName + """
         (
          id_ UUID DEFAULT random_uuid() PRIMARY KEY ,
          when_ TIMESTAMP WITHOUT TIME ZONE NOT NULL
          duration_ STRING NOT NULL
        );""";

I filed an issue to suggest the conversion if only a part of concatenation can be converted to the text block.

An alternative solution, which actually is promoted by JDK, is to use string formatting. First, use intention action “Replace ‘+’ with ‘String.format()'” (it’s available via Alt+Enter everywhere inside the concatenation). The result looks like this:

String sql = String
    .format("CREATE TABLE %s (n  id_ UUID DEFAULT random_uuid() PRIMARY KEY ,n  when_ TIMESTAMP WITHOUT TIME " +
            "ZONE NOT NULLn  duration_ STRING NOT NULLn);", tableName);

Now it’s immediately suggested to use text block, and the result looks like this:

String sql = String.format("""
        CREATE TABLE %s (
          id_ UUID DEFAULT random_uuid() PRIMARY KEY ,
          when_ TIMESTAMP WITHOUT TIME ZONE NOT NULL
          duration_ STRING NOT NULL
        );""", tableName);

Unfortunately, a new instance method .formatted() is not suggested (filed another issue), so it will require some manual work to convert it:

String sql = """
        CREATE TABLE %s (
          id_ UUID DEFAULT random_uuid() PRIMARY KEY ,
          when_ TIMESTAMP WITHOUT TIME ZONE NOT NULL
          duration_ STRING NOT NULL
        );""".formatted(tableName);
Categories
discuss

Change subtitle using CastPlayer

I do have a HLS livestream which is epresented by an URI pointing to the manifest file. The manifest file does additionally define the subtitles.

When using the ExoPlayer I can use a TrackSelector, attach it to the ExoPlayer and so I do have the option to show the available subtitles to the user (and change it through the TrackSelector).

I want to do the same with just the CastPlayer. Here the simplest possible Activity I can imagine:

 public class PlayerActivity extends AppCompatActivity implements SessionAvailabilityListener {

    private MediaRouteButton castButton;
    private CastPlayer castPlayer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.player_activity);

        castButton = findViewById(R.id.castButton);
        CastButtonFactory.setUpMediaRouteButton(this, castButton);

        CastContext castContext = CastContext.getSharedInstance(this);
        castPlayer = new CastPlayer(castContext);
        castPlayer.setSessionAvailabilityListener(this);
    }

    @Override
    public void onCastSessionAvailable() {
        MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

        MediaInfo mediaInfo = new MediaInfo.Builder("https://myuri.ch/video.m3u8")
                .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
                .setMetadata(movieMetadata)
                .build();

        MediaQueueItem mediaItem = new MediaQueueItem.Builder(mediaInfo).build();
        castPlayer.loadItems(new MediaQueueItem[]{mediaItem}, 0, 0, Player.REPEAT_MODE_OFF);
    }

    @Override
    public void onCastSessionUnavailable() {
    }
}

The layout looks like this:

    <androidx.mediarouter.app.MediaRouteButton
            android:id="@+id/castButton"
            android:layout_alignParentEnd="true"
            android:layout_alignParentTop="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
    </androidx.mediarouter.app.MediaRouteButton>

This activity starts up and when I hit the castButton it streams the movie behind “https://myuri.ch/video.m3u8” to the cast-device. Works perfectly πŸ™‚

But I can’t figure out how to allow the user to choose between subtitles from my app. What is the idea on how we should implement something like this?

Points I already found out:

  • I cannot attach something like a TrackSelector to the CastPlayer
  • The docuentation states that we can provide MediaTracks to the MediaInfo-Object. But I dont have this info, respectively it is hidden in the m3u8 file.

As additional info, my CastOptions look like this:

public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context appContext) {

        return new CastOptions.Builder()
                .setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
                .build();
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Answer

Here what I found out:

  1. The TrackSelector cannot be used for the CastPlayer.
  2. Alle the available subtitles have to be available/known before you load MediaItems to the CastPlayer
  3. The DefaultMediaReceiver needs a public available link to a *.vtt file. Something different does not work (no .webvtt nor .m3u8)
  4. The easiest way to update the subtitles on the ExoPlayer seems to be this:
        DefaultTrackSelector.ParametersBuilder parametersBuilder = trackSelector.getParameters()
                .buildUpon()
                .setRendererDisabled(getRendererIndex(C.TRACK_TYPE_TEXT), !hasSubtitles);

        if (hasSubtitles) {
            parametersBuilder.setPreferredTextLanguage("en").build();
        }

Where getRendererIndex is this:

    public int getRendererIndex(final int trackType) {
        for (int t = 0; t < exoPlayer.getRendererCount(); t++) {
            if (exoPlayer.getRendererType(t) == trackType) {
                return t;
            }
        }

        return -1;
    }
  1. To select a SubTitle on the CastPlayer you can use the RemoteMediaClient
remoteMediaClient.setActiveMediaTracks(selectedSubtitleTracks);

So my solution is this:

  1. Before starting any stream (local or cast) get all the information I need for the MediaItem (url, subtitles, …)
  2. Depending on the availability of a Cast-Session I load the MediaItems to the CastPlayer or the ExoPlayer
  3. A Custom subtitle-dialog gives the user the option to select a subtitle
  4. When the user selects a subtitle, I use the methods described above to select the subtitle on the correct player.
Categories
discuss

Anchor or Button in React SPA?

Say there is piece of text (regardless of whether it is styled as a “traditional” link or button) on a page that when clicked leads to a new page with a new page/URL. But the navigation happens programmatically (e.g. via react-router using the History web API) instead of a hard HTTP refresh.

In this case, should it be a traditional anchor link with href attribute such as # or a button?

Option 1:

<a href="#" onClick={navigateToNextPage}>Link</a>

The downside is that you have a junk href attribute. You could remove that but then it is not in the tab order and doesn’t get default link styling (though these could be overcome with one-off styling). Also if you copy the link it will copy as # which is incorrect and be interpreted incorrectly by screen readers.

Option 2:

<button onClick={navigateToNextPage}>Link</a>

The downside here is that if you want it to look like a traditional link you need to apply custom styling. Also in some ways it is really acting like a traditional link in my view. But this would be better for screen readers.

Answer

I can’t comment on React but here is the correct method for SPAs, implementing it should be trivial in React.

Short Answer

Use a hyperlink (<a>) to navigate between pages and when loading additional content if no other option is available.

More simply: if the URL changes or you add large amounts of information to the page, use a hyperlink.

Long Answer

In your question you mentioned the History API and react-router.

For this reason I assume that the function navigateToNextPage changes the URL.

I also assume that I could access that page directly if I desired by entering that URL into my browser.

With those assumptions in mind you should use:-

<a href="new-page-url" onClick={navigateToNextPage}>Link</a>

Obviously you would stop the default action (e.preventDefault() or React equivalent).

A couple of points on why to use the format described above:-

  1. Accessibility – when I encounter a hyperlink with a screen reader I am able to ask my screen reader where that link will take me, this is reassuring, I can’t do the same with a button. This is why I didn’t use # for the hyperlink but instead added the actual destination. If you see href="#" it is nearly always a sign that the wrong element is being used or it is being used incorrectly. After reading your comments about performing an action before navigating this is still perfectly valid, perform your action and then redirect, it is still navigation at the end of the day.
  2. Accessibility – when I am navigating a site via a screen reader I may decide to cycle through all the hyperlinks on the page to get a feeling for the page structure. (NVDA modifier + K to get next link for example). I am very unlikely to loop through all the buttons on a page to look for navigation.
  3. Accessibility – If I encounter a link I expect the page to change (even via AJAX). If I encounter a button I expect it to perform an action on the current page. Expected behaviour is a key part of accessibility.
  4. Accessibility – hyperlinks have some important states. ‘visited’ is a key one on pages with lots of links as I may want to review something I read earlier and being able to navigate via visited links (e.g. NVDA modifier + K for all unvisited links). Buttons do not expose this information. An important point here is that you also can’t style a button with button:visited in your CSS so you miss out on the visual clue for everybody there.
  5. AccessibilitySpace key vs the Enter key. If I land on a link I am expecting to press space to navigate, a <button> only works with the Enter key and so I may be confused as to why the page isn’t changing. (I am assuming at this point you have used a load of aria to convince me this button is a hyperlink).
  6. Robustness – If your site has limited functionality when JavaScript fails a hyperlink is far better than a button. It will still work when JavaScript fails and this is especially useful when a JavaScript failure may only be a temporary load problem with one page, allowing a user to get to another functioning page.
  7. SEO – I dare to speak of SEO on Stack Overflow? Shame! Shame! Shame! πŸ˜› – but seriously although Google is pretty darned smart in what it can do on JS powered sites it does still struggle to work out where a JavaScript only link will take it. If SEO matters for you then use a hyperlink with a valid destination so Google can map information correctly.

Probably other reasons I have forgotten to mention but I think I have made the point.

What do you have to consider when using AJAX to navigate between pages?

Although not part of your question I thought I would quickly add a couple of points for completeness.

You need to signal to a user that a page is loading if you are using a SPA pattern (and therefore interrupting normal navigation). e.g. I click your link you need to let me know that an action is being performed (loading…..) as you intercept the normal browser behaviour with e.preventDefault() or equivalent.

The simplest way is to use aria-live=assertive on a region that explains the page is loading. You can Google how to implement that correctly.

Additionally when the new page loads you need to manage focus.

The best way to do this is to add a level 1 heading (<h1>) to each page that has tabindex="-1".

Once the page loads the last action you perform in your JavaScript navigation function is to place the focus onto this heading.

This has two benefits:

  1. it lets the user know where they are now
  2. it also lets them know when the page load is complete (as AJAX navigation doesn’t announce when the page is loaded in most screen readers).

By using tabindex="-1" it means that the heading won’t be focusable by anything other than your JavaScript so won’t interfere with the normal document flow.

Categories
discuss

‘android.annotation.NonNull’ is not public in ‘android.annotation’. Cannot be accessed from outside package

'android.annotation.NonNull' is not public in 'android.annotation'. Cannot be accessed from outside package

I get this warning when I try to list data.
Does anyone know why?

Answer

You’re referring to the Android platform’s internal annotations, which aren’t the ones you want to use for your own code. For your code, you should import the AndroidX versions of those annotations:

androidx.annotation.NonNull

androidx.annotation.Nullable

etc. (note the extra x in androidx vs android)

You’ll also want to make sure you have declared a dependency on androidx.annotation:annotation in your build.gradle (see here for the latest version information)

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..