Author

Ahsen Saeed

Browsing

Recently I’ve been working on a media player application in my previous freelancing project. The application includes so much online, offline audio and video streaming. So, I tried to use Android framework classes such as MediaPlayer and AudioPlayer to play media files. As it turned out after so many hours of frustration, MediaPlayer sends out weird exceptions and  I could not see what certain crashes are happening. MediaPlayer has written natively, impossible to debug or figure out exactly what exception is happening. So, after that, I searched again and find out about ExoPlayer.  

What is ExoPlayer

ExoPlayer is an application level Media Player developed and maintained by Google. It is written in Java and it relies on low-level media. It provides so many customization options including HLSMediaSource, DashMediaSource, MP4, and others. It is used by Google apps like YouTube and Google Play Movies. 

Note: ExoPlayer only supports Android version 4.1 and above.

Android App Setup

Open the app level build.grdle file and add the ExoPlayer dependency. The ExoPlayer dependency is split into modules to allow developers to import only required dependency. If you want to learn about modularization see this post.

Below is the complete dependency of ExoPlayer.

implementation 'com.google.android.exoplayer:exoplayer:2.X.X'    // Replace X with current version.

By adding modular approach dependency your application size will be smaller and you won’t need some modules which you don’t use.

Below is the modular dependencies of ExoPlayer.

 
implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'

You have to choose which dependency you need according to your application. If you don’t know what modular dependency you required just add a complete dependency.

So, we add ExoPlayer to our app now we can play audio and video in our app seamlessly.

Play Online Audio Song

Let’s see how we can play audio song online. Below is the example of how to setup ExoPlayer for an online audio song.

BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
final ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
DataSource.Factory dateSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, getPackageName()), bandwidthMeter);
MediaSource mediaSource = new ExtractorMediaSource(Uri.parse("www.musicper.com/tere-bin.mp3"), dateSourceFactory, extractorsFactory, new Handler(), Throwable::printStackTrace);    // replace Uri with your song url
exoPlayer = ExoPlayerFactory.newSimpleInstance(this, new DefaultTrackSelector(trackSelectionFactory));
exoPlayer.prepare(mediaSource);

You guys must be thinking what on earth is this, but don’t worry most of it is a default. There is no need to spend much time to understand it. I’m afraid I have to explain this, that if you don’t want you can skip this and continue from here.

Below is the first line of code.

BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();

DefaultBandwidthMeter class implies estimated available network bandwidth based on measured download speed.

Below is the second line of code.

final ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();

An ExtractorFactory is there for providing an array of the extractor for the media formats. You can see the list of formats providing by ExtractorFactory here. 

Below is the third line of code.

TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);

AdaptiveTrackSelection.Factory is a bandwidth based adaptive TrackSelectionIt gives the highest quality state of a buffer according to your network condition.

Below is the fourth line of code.

DataSource.Factory dateSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, getPackageName()), defaultBandwidthMeter);

DefaultDataSourceFactory supports almost all non-adaptive audio video formats supported on Android. It will recognize our mp3 file and play it nicely. You can learn more about DefaultDataSourceFactory here.

Below is the fifth line of code.

MediaSource mediaSource = new ExtractorMediaSource(Uri.parse("www.musicper.com/tere-bin.mp3"), dateSourceFactory, extractorsFactory, new Handler(), Throwable::printStackTrace);   // Replace Uri with your song url

ExtractorMediaSource is the main part where all of our previous objects concat in it. It is suitable for regular media files. It is representing the media which we’re going to play.

Below is the sixth line of code.

exoPlayer = ExoPlayerFactory.newSimpleInstance(this, new DefaultTrackSelector(trackSelectionFactory));

In here we’re only creating an instance of ExoPlayer.  A factory for ExoPlayer instance. For to read more about ExoPlayer see this link.

Below is the seventh line of code.

exoPlayer.prepare(mediaSource);

You see in here we’re setting media source to our ExoPlayer instance. So, now we created the SimpleExoPlayer instance as an object to make it accessible to all methods in the class.

Now it’s time to see how we can play some music with the ExoPlayer instance. After preparing the media source we just need to set the boolean value to an ExoPlayer instance.

Below is the example of how to play a song.

exoPlayer.setPlayWhenReady(true);

The setPlayWhenReady method will start the media when it is ready. If the player is already in the ready state then this method can be used to pause and resume playback.

You can also mute or unmute with an ExoPlayer instance.

exoPlayer.setVolume(0f);              // It is used for mute
exoPlayer.setVolume(1f);              // It is usedfor unmute

We have to release our player when no longer in use to save resources. The following shows how to release the player.

if (player!=null) {
        player.release();
        player = null;
    }

Playlist Of Online Audio Song

Sometimes we need to play songs in a list. For this purpose, we can use ConcatenatingMediaSource instead of ExtractorMediaSourceThe following shows how we can make ConcatenatingMediaSource of a list of songs.

BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
final ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
DataSource.Factory dateSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, getPackageName()), bandwidthMeter);
MediaSource[] mediaSources = new MediaSource[songList.size()];
for (int i = 0; i < mediaSources.length; i++) {
    String songUri = songList.get(i).getUri();
    mediaSources[i] = new ExtractorMediaSource(Uri.parse(songUri), dateSourceFactory, extractorsFactory, null, Throwable::printStackTrace);
}
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
        : new ConcatenatingMediaSource(mediaSources);
exoPlayer = ExoPlayerFactory.newSimpleInstance(this, new DefaultTrackSelector(trackSelectionFactory));
exoPlayer.prepare(mediaSource);

To have a callback when the new song is going to play, something bad happened during playing, the song is buffering and position discontinuity. If you want to get notified every time when these things happened you need to add a listener to an exoPlayer instance.

The following shows how you can add the listener to an exoPlayer.

exoPlayer.addListener(new Player.EventListener() {
            @Override
            public void onTimelineChanged(Timeline timeline, Object manifest) {
            }

            @Override
            public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
            }

            @Override
            public void onLoadingChanged(boolean isLoading) {
            }

            @Override
            public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
            }

            @Override
            public void onRepeatModeChanged(int repeatMode) {
            }

            @Override
            public void onPlayerError(ExoPlaybackException error) {
            }

            @Override
            public void onPositionDiscontinuity() {
            }

            @Override
            public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
            }
        });

Play Offline Audio Streaming

Playing offline audio song pretty much the same playing song online.

Below is the example of how you can play the offline song.

RenderersFactory renderersFactory = new DefaultRenderersFactory(this, null, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
TrackSelector trackSelector = new DefaultTrackSelector();
ExtractorFactory extractorFactory = new DefaultExtractorsFactory();
DataSource dataSource = new DefaultDataSourceFactory(this,Util.getUserAgent(this, "ExoPlayerIntro"));
MediaSource mediaSource = new ExtractorMediaSource(uri, dataSource, extractorFactory, new Handler(), Throwable::printStackTrace);
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
simpleExoPlayer.prepare(mediaSource);
simpleExoPlayer.setPlayWhenReady(true);

You see, this is the same except for DefaultRenderersFactory instance. For offline streaming, we just need to add EXTENSION_RENDERER_MODE_OFF. 

Playlist Of Offline Audio Streaming

Now, it’s time to see how we can play the playlist of offline songs. I’m pretty sure if you guy’s working on media player app this would have been your requirement.

Below is the example of how to play the playlist of offline songs.

RenderersFactory rederersFactory = new DefaultRenderersFactory(this, null, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
TrackSelector trackSelector = new DefaultTrackSelector();
ExtractorFactory extractorFactory = new DefaultExtractorsFactory();
DataSource dataSource = new DefaultDataSourceFactory(this,Util.getUserAgent(this, "ExoPlayerIntro"));
MediaSource[] mediaSources = new MediaSource[songList.size()];
for (int i = 0; i < mediaSources.length; i++) {
       String s = songList.get(i).getSongId();
       mediaSources[i] = new ExtractorMediaSource(Uri.parse(s), dateSourceFactory, extractorsFactory, null, Throwable::printStackTrace);
}
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]: new ConcatenatingMediaSource(mediaSources);
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
simpleExoPlayer.prepare(mediaSource);
simpleExoPlayer.setPlayWhenReady(true);

That’s, it guys this is my explanation about playing audio song online and offline with ExoPlayer.

I hope this blog gives you a good understanding of how to play the audio song with ExoPlayer. If you’ve any queries please do comment below.

Thank you for being here and keep reading.

You might be Interested In: QRCode Generator And Reader Android Example

Hi, guys today we’re going to see how we can animate marker on google map with user current location. In this blog, we’re going to use the Google Map API for google maps. I’m not going to show how you can get Google Map API from Google Console. If you want to see how to get Google MAP API to see this on my blog. So, without further ado let’s dive into AndroidStudio and do some coding.

Android App Setup

First, open your app level build.gradle file and add the dependency for location and google map.

implementation 'com.google.android.gms:play-services-location:15.0.1'
implementation 'com.google.android.gms:play-services-maps:15.0.1'

Now open the Manifest file and add permission for Location and Internet.

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />

Now add the meta tag in the Manifest file for Google Map API and for play services version.

<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="Aalza*******************************qw23" />    // Replace this with your google map api key

<meta-data
    android:name="com.google.android.gms.version"
    android:value="@integer/google_play_services_version" />

Add SupportMapFragment to your xml file where you want to show Google Map. Below is the xml file.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/mapFragment"
        class="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageButton
        android:id="@+id/currentLocationImageButton"
        android:layout_width="40dp"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:layout_marginBottom="25dp"
        android:layout_marginEnd="15dp"
        android:contentDescription="@null"
        android:src="@drawable/current_location_vector_icon" />

</RelativeLayout>

As you can see the xml file is much simpler. In here we have SupportMapFragment and current location button. The button is for when user taps on, it navigates to the current location on the map.

Done with the xml file now go to your java file in onCreate method get the SupportMapFragment object and add OnMapReadyCallackBelow is the code for SupportMapFragment.

SupportMapFragment supportMapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.mapFragment);
supportMapFragment.getMapAsync(this);

Below is the overriding method of OnMapReadyCallback in java file.

@Override
public void onMapReady(GoogleMap googleMap) {
    this.googleMap = googleMap;
}

We need to save the instance of GoogleMaps because later we have to set the current location and marker reference on it.

Now we have to see if the user has previously installed GooglePlayServices on his/her mobile. It’s a good approach that you check GooglePlayServices installed or not. Below is the code for checking GooglePlayServices.

@Override
protected void onResume() {
 super.onResume();
 if (isGooglePlayServicesAvailable()) {
 fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
 startCurrentLocationUpdates();
 }
}

You can add this code in the onResume method. isGooglePlayServicesAvailable is our custom method to check that GooglePlayServices available or not. If the user does not have GooglePlayServices then we simply show a Toast else we start current location updates.




Below is the isGooglePlayServicesAvailable method.

private boolean isGooglePlayServicesAvailable() {
    GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
    int status = googleApiAvailability.isGooglePlayServicesAvailable(this);
    if (ConnectionResult.SUCCESS == status)
        return true;
    else {
        if (googleApiAvailability.isUserResolvableError(status))
            Toast.makeText(this, "Please Install google play services to use this application", Toast.LENGTH_LONG).show();
    }
    return false;
}

GoogleApiAvailability is the utility class to check GooglePlayServices availability. If you want to learn more about GoogleApiAvailability class see this link.

Below is the startCurrentLocationUpdates method.

private void startCurrentLocationUpdates() {
    LocationRequest locationRequest = LocationRequest.create();
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    locationRequest.setInterval(3000);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                    MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
            return;
        }
    }
    fusedLocationProviderClient.requestLocationUpdates(locationRequest, mLocationCallback, Looper.myLooper());
}

LocationRequest is the class to tell how much time interval we need after every location and also we can set location Accuracy with the LocationRequest. In here we’re also checking if the app has the location permission or not. If not then we’re asking runtime permission for location else it continues. Now we just need the LocationCallback class object which we’re passing in when requesting location updates.

private final LocationCallback mLocationCallback = new LocationCallback() {

    @Override
    public void onLocationResult(LocationResult locationResult) {
        super.onLocationResult(locationResult);
        if (locationResult.getLastLocation() == null)
            return;
        currentLocation = locationResult.getLastLocation();
        if (firstTimeFlag && googleMap != null) {    
            animateCamera(currentLocation);
            firstTimeFlag = false;
        }
        showMarker(currentLocation);
    }
};

You see in here we’re saving user location in our currentLocation object because we need the location for when user tap on current location button. After that, we check if firstTimeFlag is a true and googleMap instance not null. I’m checking this condition because when the app opens we need to animate GoogleMaps to user current location. This if condition true only first time.

Below is the code for animateCamera method.

private void animateCamera(@NonNull Location location) {
    LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
    googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(getCameraPositionWithBearing(latLng)));
}

Below is the code for showMarker method.

private void showMarker(@NonNull Location currentLocation) {
    LatLng latLng = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
    if (currentLocationMarker == null)
        currentLocationMarker = googleMap.addMarker(new MarkerOptions().icon(BitmapDescriptorFactory.defaultMarker()).position(latLng));
    else
        MarkerAnimation.animateMarkerToGB(currentLocationMarker, latLng, LatLngInterpolator.Spherical());
}

Note: This is the main part of our blog. In here first, we check if the currentLocationMarker equal to null. Then we simply add a new marker on the map. If not then we call our AnimatingMarkerHelper class to animate the marker from the previous location to user new location.

Below is the code of AnimatingMarkerHelper.java class.

public class MarkerAnimation {

    public static void animateMarkerToGB(final Marker marker, final LatLng finalPosition, final LatLngInterpolator latLngInterpolator) {
        final LatLng startPosition = marker.getPosition();
        final Handler handler = new Handler();
        final long start = SystemClock.uptimeMillis();
        final Interpolator interpolator = new AccelerateDecelerateInterpolator();
        final float durationInMs = 2000;

        handler.post(new Runnable() {
            long elapsed;
            float t;
            float v;

            @Override
            public void run() {
                // Calculate progress using interpolator
                elapsed = SystemClock.uptimeMillis() - start;
                t = elapsed / durationInMs;
                v = interpolator.getInterpolation(t);

                marker.setPosition(latLngInterpolator.interpolate(v, startPosition, finalPosition));

                // Repeat till progress is complete.
                if (t < 1) {
                    // Post again 16ms later.
                    handler.postDelayed(this, 16);
                }
            }
        });
    }
}

In the animateMarkerToGB method, we’re passing LatLngInterpolator instance. Below is the LatLngInterpolator class.

public interface LatLngInterpolator {

    LatLng interpolate(float fraction, LatLng a, LatLng b);

    class Spherical implements LatLngInterpolator {

        /* From github.com/googlemaps/android-maps-utils */
        @Override
        public LatLng interpolate(float fraction, LatLng from, LatLng to) {
            // http://en.wikipedia.org/wiki/Slerp
            double fromLat = toRadians(from.latitude);
            double fromLng = toRadians(from.longitude);
            double toLat = toRadians(to.latitude);
            double toLng = toRadians(to.longitude);
            double cosFromLat = cos(fromLat);
            double cosToLat = cos(toLat);

            // Computes Spherical interpolation coefficients.
            double angle = computeAngleBetween(fromLat, fromLng, toLat, toLng);
            double sinAngle = sin(angle);
            if (sinAngle < 1E-6) {
                return from;
            }
            double a = sin((1 - fraction) * angle) / sinAngle;
            double b = sin(fraction * angle) / sinAngle;

            // Converts from polar to vector and interpolate.
            double x = a * cosFromLat * cos(fromLng) + b * cosToLat * cos(toLng);
            double y = a * cosFromLat * sin(fromLng) + b * cosToLat * sin(toLng);
            double z = a * sin(fromLat) + b * sin(toLat);

            // Converts interpolated vector back to polar.
            double lat = atan2(z, sqrt(x * x + y * y));
            double lng = atan2(y, x);
            return new LatLng(toDegrees(lat), toDegrees(lng));
        }

        private double computeAngleBetween(double format, double fromLng, double toLat, double toLng) {
            // Haversine's formula
            double dLat = fromLat - toLat;
            double dLng = fromLng - toLng;
            return 2 * asin(sqrt(pow(sin(dLat / 2), 2) +
                    cos(fromLat) * cos(toLat) * pow(sin(dLng / 2), 2)));
        }
    }
}

Now it’s a good approach that you stopped the location updates in the onStop method. Below is the code how to stop location updates.

@Override
protected void onStop() {
    super.onStop();
    if (fusedLocationProviderClient != null)
        fusedLocationProviderClient.removeLocationUpdates(mLocationCallback);
}

That’s it, this is the complete example of how to animate marker on google maps. For complete source code of the above example see it on GitHub. If you guy’s want to learn how to show current location on GoogleMapStreetView see this example.

I hope this blog gives you a good understanding of how to animate a marker on google map. If you’ve any queries please do comment below.

Thank you for being here and keep reading…

Hey guy’s, today we’re going to learn how can we download a file, mp3, ppt or image from the internet with DownloadManager. Downloading a file from the internet is a basic need of the app right. So, with DownloadManager it’s very easy.

DownloadManager is an android system service that handles long-running HTTP downloads. Apps request for downloads by passing a URI or also tell the destination folder in where you want to store that specific file.

Note: DownloadManager only supports HTTP protocol.

So, enough of this theory let’s see how we can integrate DownloadManager in our Android app.

Android App Setup

First, start with the AndroidManifest file. Open the AndroidManifest file and add permission for internet and storage.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Below is code for the activity_main.xml file.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/downloadImageButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/download_image" />

    <Button
        android:id="@+id/downloadSongButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="@string/download_song" />

</LinearLayout>

This is a very simple UI. In this UI we have only two buttons one is for download image and another one is for download song from the server.

Now go MainActivity.java class and get these buttons reference and add ClickListener on these buttons.

findViewById(R.id.downloadImageButton).setOnClickListener(this);
findViewById(R.id.downloadSongButton).setOnClickListener(this);

Below is the abstract method of View.OnClickListener interface.

@Override
  public void onClick(View view) {
      switch (view.getId()) {
          case R.id.downloadImageButton: {
              startService(DownloadSongService.getDownloadService(this, IMAGE_DOWNLOAD_PATH, DirectoryHelper.ROOT_DIRECTORY_NAME.concat("/")));
              break;
          }
          case R.id.downloadSongButton: {
              startService(DownloadSongService.getDownloadService(this, SONG_DOWNLOAD_PATH, DirectoryHelper.ROOT_DIRECTORY_NAME.concat("/")));
              break;
          }
      }
  }

DownloadSongService is the IntentServiceWhen the button is clicked we start DownloadSongService. Another thing of noticeable here is the DirectoryHelper class. DirectoryHelper is the util class for storage permission. This util class helps us to create directories to store image and song.

Below is the DownloadSongService class.

public class DownloadSongService extends IntentService {

    private static final String DOWNLOAD_PATH = "com.spartons.androiddownloadmanager_DownloadSongService_Download_path";
    private static final String DESTINATION_PATH = "com.spartons.androiddownloadmanager_DownloadSongService_Destination_path";

    public DownloadSongService() {
        super("DownloadSongService");
    }

    public static Intent getDownloadService(final @NonNull Context callingClassContext, final @NonNull String downloadPath, final @NonNull String destinationPath) {
        return new Intent(callingClassContext, DownloadSongService.class)
                .putExtra(DOWNLOAD_PATH, downloadPath)
                .putExtra(DESTINATION_PATH, destinationPath);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String downloadPath = intent.getStringExtra(DOWNLOAD_PATH);
        String destinationPath = intent.getStringExtra(DESTINATION_PATH);
        startDownload(downloadPath, destinationPath);
    }

    private void startDownload(String downloadPath, String destinationPath) {
        Uri uri = Uri.parse(downloadPath); // Path where you want to download file.
        DownloadManager.Request request = new DownloadManager.Request(uri);
        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);  // Tell on which network you want to download file.
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);  // This will show notification on top when downloading the file.
        request.setTitle("Downloading a file"); // Title for notification.
        request.setVisibleInDownloadsUi(true);
        request.setDestinationInExternalPublicDir(destinationPath, uri.getLastPathSegment());  // Storage directory path
        ((DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request); // This will start downloading 
    }
}

We also need to configure our service in AndroidManifest.xml. If we do not add service to the Manifest file then the service never going to start.

The following shows you how you can add service tag in AndroidManifest.xml file.

<service
    android:name=".DownloadSongService"
    android:enabled="true"
    android:exported="false" />

That’s it guy’s if you want to see the complete example see it on GitHub. You guys must be thinking where is the DirectoryHelper class. I thought it might not good to explain about storage in DownloadManager tutorial but, you can see it on GitHub with the complete example.

I hope this blog gives you a good understanding of DownloadManager. If you’ve any queries please do comment below.

 

Topics, you might be interested: Android WorkManager Example

or Online Offline Audio Streaming With ExoPlayer

Hey guy’s, today we’re going to learn how we can show the current location of the user on GoogleMapStreetView. I’m going to reuse the code from my previous blog => Android Google Map Street View Example.

The previous Article was about how to show GoogleMapStreetView in Android app. In this blog, we’re not going to set up Google Map Console because I have done this in my previous blog. So, without further ado let’s dive into AndroidStudio and do some coding.

 Android App Setup

First, open your app level build.gradle file and add the dependency for location.

implementation 'com.google.android.gms:play-services-location:15.0.0'

Now open the Manifest file to add app location and internet permission.

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />

Now add StreetViewPanoramaFragment to activity_main.xml. Below is the activity_main.xml file.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/streetViewMap"
        android:name="com.google.android.gms.maps.StreetViewPanoramaFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

Now go to the MainActivity.java file and get the StreetViewPanoramaFragment object and add OnStreetViewPanoramaReadyCallback. Below is the code for the StreetViewPanoramaFragment object.

streetViewPanoramaFragment = (StreetViewPanoramaFragment) getFragmentManager()
                .findFragmentById(R.id.streetViewMap);
        streetViewPanoramaFragment.getStreetViewPanoramaAsync(this);

Now we have to see if the user has previously installed GooglePlayServices on his/her mobile. Below is the example of how we can check the GooglePlayServices is installed or not.

if (!isGooglePlayServicesAvailable())
       Toast.makeText(this, "Google play service not available", Toast.LENGTH_SHORT).show();
else {
       fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
       startCurrentLocationUpdates();
}

isGooglePlayServicesAvailable is our custom method to check that GooglePlayServices available or not. If the user does not have GooglePlayServices then we simply show a Toast else we start current location updates.

Below is the isGooglePlayServicesAvailable method.

private boolean isGooglePlayServicesAvailable() {
    GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
    int status = googleApiAvailability.isGooglePlayServicesAvailable(this);
    if (ConnectionResult.SUCCESS == status)
        return true;
    else {
        if (googleApiAvailability.isUserResolvableError(status))
            Toast.makeText(this, "Please Install google play services to use this application", Toast.LENGTH_LONG).show();
        else
            googleApiAvailability.getErrorDialog(this, status, API_AVAILABILITY_REQUEST_CODE).show();
    }
    return false;
}

GoogleApiAvailability is the utility class to check GooglePlayServices availability. If you want to learn more about GoogleApiAvailability class see this link.

Below is the startCurrentLocationUpdates method.

private void startCurrentLocationUpdates() {
    LocationRequest locationRequest = LocationRequest.create();
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    locationRequest.setInterval(3000);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                    MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
            return;
        }
    }
    fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
}

LocationRequest is the class to tell how much time interval we need after every location and also we can set location Accuracy with the LocationRequest. Now we just need the LocationCallback class object which we’re passing in when requesting location updates.

Below is the code how can we make the LocationCallback object.

private LocationCallback locationCallback = new LocationCallback() {

    @Override
    public void onLocationResult(LocationResult locationResult) {
        super.onLocationResult(locationResult);
        Location location = locationResult.getLastLocation();
        if (location == null)
            return;
        LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
        streetViewPanorama.setPosition(latLng);
        // If you do not want to cancel the location updates after receiving the first location then comment the below line.
        fusedLocationProviderClient.removeLocationUpdates(locationCallback);
    }
};

onLocationResult is the method where we’re actually getting our current location. Now we just need to set the location into StreetViewPanorama object.

Below is the complete MainActivity.java code.

package com.spartons.googlemapstreetviewlocation;

import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.location.Location;
import android.os.Build;
import android.os.Looper;
import android.os.PersistableBundle;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.OnStreetViewPanoramaReadyCallback;
import com.google.android.gms.maps.StreetViewPanorama;
import com.google.android.gms.maps.StreetViewPanoramaFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.StreetViewPanoramaCamera;

public class MainActivity extends AppCompatActivity implements OnStreetViewPanoramaReadyCallback {

    private static final Integer PANORAMA_CAMERA_DURATION = 1000;
    public static final String TAG = MainActivity.class.getSimpleName();
    private static final String STREET_VIEW_BUNDLE = "StreetViewBundle";
    private static final int API_AVAILABILITY_REQUEST_CODE = 45656;
    private static final int MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 48499;

    private StreetViewPanorama streetViewPanorama;
    private StreetViewPanoramaFragment streetViewPanoramaFragment;
    private FusedLocationProviderClient fusedLocationProviderClient;

    private StreetViewPanorama.OnStreetViewPanoramaChangeListener streetViewPanoramaChangeListener = streetViewPanoramaLocation -> Log.e(TAG, "Street View Panorama Change Listener");

    private StreetViewPanorama.OnStreetViewPanoramaClickListener streetViewPanoramaClickListener = (orientation -> {
        Point point = streetViewPanorama.orientationToPoint(orientation);
        if (point != null) {
            streetViewPanorama.animateTo(
                    new StreetViewPanoramaCamera.Builder()
                            .orientation(orientation)
                            .zoom(streetViewPanorama.getPanoramaCamera().zoom)
                            .build(), PANORAMA_CAMERA_DURATION);
        }
    });

    private LocationCallback locationCallback = new LocationCallback() {

        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);
            Location location = locationResult.getLastLocation();
            if (location == null)
                return;
            LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
            Log.e(TAG, latLng.latitude + " " + latLng.longitude);
            streetViewPanorama.setPosition(latLng);
            // If you do not want to cancel the location updates after receiving the first location then comment the below line.
            fusedLocationProviderClient.removeLocationUpdates(locationCallback);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        streetViewPanoramaFragment = (StreetViewPanoramaFragment) getFragmentManager()
                .findFragmentById(R.id.streetViewMap);
        streetViewPanoramaFragment.getStreetViewPanoramaAsync(this);
        Bundle streetViewBundle = null;
        if (savedInstanceState != null)
            streetViewBundle = savedInstanceState.getBundle(STREET_VIEW_BUNDLE);
        streetViewPanoramaFragment.onCreate(streetViewBundle);
        if (!isGooglePlayServicesAvailable())
            Toast.makeText(this, "Google play service not available", Toast.LENGTH_SHORT).show();
        else {
            fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
            startCurrentLocationUpdates();
        }
    }

    private void startCurrentLocationUpdates() {
        LocationRequest locationRequest = LocationRequest.create();
        locationRequest
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
                .setInterval(3000);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
                return;
            }
        }
        fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
    }

    private boolean isGooglePlayServicesAvailable() {
        GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
        int status = googleApiAvailability.isGooglePlayServicesAvailable(this);
        if (ConnectionResult.SUCCESS == status)
            return true;
        else {
            if (googleApiAvailability.isUserResolvableError(status))
                Toast.makeText(this, "Please Install google play services to use this application", Toast.LENGTH_LONG).show();
            else
                googleApiAvailability.getErrorDialog(this, status, API_AVAILABILITY_REQUEST_CODE).show();
        }
        return false;
    }

    @Override
    protected void onResume() {
        super.onResume();
        streetViewPanoramaFragment.onResume();
    }

    @Override
    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
        super.onSaveInstanceState(outState, outPersistentState);
        Bundle mStreetViewBundle = outState.getBundle(STREET_VIEW_BUNDLE);
        if (mStreetViewBundle == null) {
            mStreetViewBundle = new Bundle();
            outState.putBundle(STREET_VIEW_BUNDLE, mStreetViewBundle);
        }
        streetViewPanoramaFragment.onSaveInstanceState(mStreetViewBundle);
    }

    @Override
    public void onStreetViewPanoramaReady(StreetViewPanorama streetViewPanorama) {
        this.streetViewPanorama = streetViewPanorama;
        this.streetViewPanorama.setOnStreetViewPanoramaChangeListener(streetViewPanoramaChangeListener);
        this.streetViewPanorama.setOnStreetViewPanoramaClickListener(streetViewPanoramaClickListener);
    }

    @Override
    protected void onStop() {
        super.onStop();
        streetViewPanoramaFragment.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (streetViewPanoramaFragment != null)
            streetViewPanoramaFragment.onDestroy();
        streetViewPanoramaChangeListener = null;
        streetViewPanoramaClickListener = null;
        streetViewPanorama = null;
        if (fusedLocationProviderClient != null && locationCallback != null)
            fusedLocationProviderClient.removeLocationUpdates(locationCallback);
        fusedLocationProviderClient = null;
        locationCallback = null;
    }
}

That’s it, this is the complete example of how you can show the current location of the user on StreetViewGoogleMap. Complete source code for the above example is on GitHub.

Download Complete Code

I hope this blog gives you a good understanding of how you can show the current location on StreetViewGoogleMap. If you’ve any queries for the post please do comment below.

Hey, today we’re going to learn how we can integrate Google Map Street View into our Android app. Google Street View provides panoramic 360* views. The coverage available through the Google Map Android API v2 is the same as that for Google Map. You can read more about Street View here.

Google Map Setup 

First, sign into your Google console developer account and Create Project. After that go to Library section and search for Android Map API. After that select and enable the key. When you enabled the API key go to Credential sections and copy the API key. It looks like AIzaSyD2gPrI2eAR*****************hoyI this.

 Android App Setup

Create an empty project and open app level build.gradle file. After that add Google Map dependency.

implementation 'com.google.android.gms:play-services-maps:15.0.0'

Now go to Manifest file and add meta tags in application tag.

<meta-data
    android:name="com.google.android.gms.version"
    android:value="@integer/google_play_services_version" />

<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="Paste Google Map API key" />

The first meta tag is for Google play services version. The second one is for Google Map API key.

Now the setup part is done, to show the Street Map add StreetViewPanoramaFragment into activity_main.xml. Below is the activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/streetViewMap"
        android:name="com.google.android.gms.maps.StreetViewPanoramaFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

Now go to the activity and get the StreetViewPanoramaFragment object and add OnStreetViewPanoramaReadyCallback. Below is the code for the StreetViewPanoramaFragment object.

streetViewPanoramaFragment = (StreetViewPanoramaFragment) getFragmentManager()
                .findFragmentById(R.id.streetViewMap);
        streetViewPanoramaFragment.getStreetViewPanoramaAsync(this);

For StreetViewPanoramaCallback we’re setting our activity reference. It means our activity has to override the abstract method of the callback. Below is the code for the StreetViewPanoramaCallback abstract method.

@Override
  public void onStreetViewPanoramaReady(StreetViewPanorama streetViewPanorama) {
      streetViewPanorama.setPosition(new LatLng(-33.873398, 150.976744));
      streetViewPanorama.setOnStreetViewPanoramaChangeListener(streetViewPanoramaChangeListener);
      streetViewPanorama.setOnStreetViewPanoramaClickListener(streetViewPanoramaClickListener);
  }

To set the location of StreetViewPanorama call setPosition method passing a LatLng object. You can also register the listener for when the user clicks on StreetViewMap or when the StreetViewPanorama change.

Below is the code for StreetViewMap click listener.

private StreetViewPanorama.OnStreetViewPanoramaClickListener streetViewPanoramaClickListener = new StreetViewPanorama.OnStreetViewPanoramaClickListener() {
 @Override
 public void onStreetViewPanoramaClick(StreetViewPanoramaOrientation streetViewPanoramaOrientation) {
 Point point = streetViewPanorama.orientationToPoint(streetViewPanoramaOrientation);
 if (point != null) {
 streetViewPanorama.animateTo(
 new StreetViewPanoramaCamera.Builder()
 .orientation(streetViewPanoramaOrientation)
 .zoom(streetViewPanorama.getPanoramaCamera().zoom)
 .build(), 1000);
 }
 }
};

onStreetViewPanorama method hits every time when the user clicks on the map. Also, we get StreetViewPanoramaOrientation object as a parameter, with a streetViewPanoramaOrientation object we can get the Point where the user clicks and simply animate to that point.

Below is the code for StreetViewPanorama change listener.

private StreetViewPanorama.OnStreetViewPanoramaChangeListener streetViewPanoramaChangeListener = new StreetViewPanorama.OnStreetViewPanoramaChangeListener() {
    @Override
    public void onStreetViewPanoramaChange(StreetViewPanoramaLocation streetViewPanoramaLocation) {
        Log.e(TAG, "Street View Panorama Change Listener");
    }
};

onStreetViewPanoramaChange method hits every time when we setPosition to StreetViewMap. 

You can enable and disable user controlled gestures through methods on StreetViewPanorama.

Below is the code how you can disable the street names displayed on the ground. The default is true.

streetViewPanorama.setStreetNamesEnabled(false);

Below is the code how you can disable pinch zoom. The default is true.

streetViewPanorama.setZoomGesturesEnabled(false);

Below is the code how the user will be able to re-orient the camera by dragging.

streetViewPanorama.setPanningGesturesEnabled(false);

You can also set zoom level programmatically. Setting the zoom to 1.0 will magnify the image by a factor of 2. Below is the code how to set zoom level.

private static final float ZOOM_BY = 0.5f;
streetViewPanorama.animateTo(new StreetViewPanoramaCamera.Builder()
    .zoom(mStreetViewPanorama.getPanoramaCamera().zoom + ZOOM_BY)
    .build();

It’s a good approach to resume, stop and destroy the StreetViewPanoramaFragment object throughout the activity life-cycle.

@Override
protected void onResume() {
    super.onResume();
    streetViewPanoramaFragment.onResume();
}

@Override
protected void onStop() {
    super.onStop();
    streetViewPanoramaFragment.onStop();
}
@Override
protected void onDestroy() {
    super.onDestroy();
    if (streetViewPanoramaFragment != null)
        streetViewPanoramaFragment.onDestroy();
}

This is how you can use the activity life-cycle with the StreetViewPanoramaFragment object.

You can also store the state of StreetViewPanoramaFragment instance in an onSaveInstanceState method. Below is the example how you can store the state.

@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
    super.onSaveInstanceState(outState, outPersistentState);
    Bundle mStreetViewBundle = outState.getBundle(STREET_VIEW_BUNDLE);
    if (mStreetViewBundle == null) {
        mStreetViewBundle = new Bundle();
        outState.putBundle(STREET_VIEW_BUNDLE, mStreetViewBundle);
    }
    streetViewPanoramaFragment.onSaveInstanceState(mStreetViewBundle);
}

That’s it, I’m going to complete this blog here. If you guys want to learn how you can show the current location of a user on GoogleStreetViewMap, see this => Current Location With Google Map Street View Android.

Complete source code for the above example sees on GitHub.

Download Complete Code

I hope this blog gives you a good understanding of how you can add GoogleStreetViewMap in Android app. If you’ve any queries please do comment below.

Topic, You might be interested: Android Example : Animate marker on Google map with current location

 

I’ve met this problem with Recycler View. When I update a view, like changing the CheckBox, SwitchCompat, change the background color of view etc. After that when I scroll down or go up the view back to its normal position like the changes that I made it’s gone from view. It is because RecyclerView recycles every view which is not visible to the user. So, when the user scrolls back it recycles the previous view which is in normal form.

So, today I’m going to explain how we can save the view state.

The following shows the model we’re going to use in this example.

public class BookModel {

    private String bookName;
    private boolean wantToReadFlag;
    private int backgroundColor;

    public BookModel(String bookName) {
        this.bookName = bookName;
        wantToReadFlag = false;
        backgroundColor = Color.Grey;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public boolean isWantToReadFlag() {
        return wantToReadFlag;
    }

    public void setWantToReadFlag(boolean wantToReadFlag) {
        this.wantToReadFlag = wantToReadFlag;
    }
    
    public void setBackgroundColor(int backgroundColor){
        this.backgroundColor = backgroundColor;
    }
    
    public int getBackgroundColor(){
        return backgroundColor;
    }

    @Override
    public String toString() {
        return "BookModel{" +
                "bookName='" + bookName + '\'' +
                ", wantToReadFlag=" + wantToReadFlag +
                '}';
    }
}

The bookName is named for the book which we’re showing. The wantToReadFlag is for CheckBox, when the user checked checkBox we store a true value to model.

Below is code for RecyclerViewAdapter.

public class BookAdapter extends RecyclerView.Adapter<BookAdapter.MyViewHolder> {

    private final LayoutInflater layoutInflater;
    private List<BookModel> bookModels;

    public BookAdapter(List<BookModel> bookModels, final Context context) {
        this.bookModels = bookModels;
        this.layoutInflater = LayoutInflater.from(context);
    }

    @NonNull
    @Override
    public BookAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MyViewHolder(layoutInflater.inflate(R.layout.book_single_view, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull BookAdapter.MyViewHolder holder, int position) {
        BookModel bookModel = bookModels.get(position);
        holder.bookNameTextView.setText(bookModel.getBookName());
        if (bookModel.isWantToReadFlag())     
            holder.wantToReadCheckBox.setChecked(true);
        else
            bookModel.setWantToReadFlag(false);
        holder.setICheckChangeListener(new ICheckChangeListener() {
            @Override
            public void onItemChecked(int position, boolean value) {
                bookModels.get(position).setWantToReadFlag(value);
            }
        });
    }

    @Override
    public int getItemCount() {
        return bookModels.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        private TextView bookNameTextView;
        private CheckBox wantToReadCheckBox;
        private ICheckChangeListener iCheckChangeListener;

        private MyViewHolder(View itemView) {
            super(itemView);
            bookNameTextView = itemView.findViewById(R.id.bookNameTextView);
            wantToReadCheckBox = itemView.findViewById(R.id.wantToReadCheckBox);
            wantToReadCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                    iCheckChangeListener.onItemChecked(getAdapterPosition(), b);
                }
            });
        }

        void setICheckChangeListener(ICheckChangeListener iCheckChangeListener) {
            this.iCheckChangeListener = iCheckChangeListener;
        }
    }
}

In onBindViewHolder, we have a setOnCheckChangeListener method which is called every time when user change CheckBox.  So, when it is called we stored a flag value in our BokkModel class. Now when the user scrolls bottom or comes back to the previous position the view never changes.

You can also add ClickListener on itemView when the user taps on the row you can change the background color and store in the model.

The following example shows how you can store the state of view with the background color. I’m only implementing the onBindView method.

@Override
public void onBindViewHolder(@NonNull BookAdapter.MyViewHolder holder, int position) {
    BookModel bookModel = bookModels.get(position);
    holder.itemView.setBackgroundColor(bookModel.getBackgroundColor());
    holder.bookNameTextView.setText(bookModel.getBookName());
    if (bookModel.isWantToReadFlag())     
        holder.wantToReadCheckBox.setChecked(true);
    else
        bookModel.setWantToReadFlag(false);
    holder.setICheckChangeListener(new ICheckChangeListener() {
        @Override
        public void onItemChecked(int position, boolean value) {
            bookModels.get(position).setWantToReadFlag(value);
        }
    });
     holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            bookModels.get(position).setBackgroundColor(Color.Red);
        }
    });
}

Below is the ICheckChangeListener interface.

public interface ICheckChangeListener {

    void onItemChecked(int position, boolean value);
}

That’s the interface which we implemented in our ViewHolder class.

See the complete example on GitHub.

Download Complete Code

That’s it now the view never changes it states on the scroll.

I hope this blog gives you a good understanding of RecyclerView. If you’ve any queries please do comment below.

 

You might be interested in: Beautiful Login Screen For Android With Example

or Basic Animation Of Android Views With Example

Hey, guys today we’re going to learn about RxJava. RxJava stands for a Reactive extension. RxJava is one of the most popular libraries for reactive programming. Reactive programming basically provides a simple way of asynchronous programming. RxJava follows the Observer pattern.

People are gonna say’s you like asynchronous is complex, it’s very hard. All this true working with asynchronous is difficult but RxJava library gives you a very simple way to convert your task in to asynchronously very easily. You can achieve this thing with simple java but let me tell you this not an easy thing to do.

The first thing you know about RxJava is there are three constructs.

The first construct is Observable.

Observable:  Observable are the sources for data to emit. An observable start providing data once a subscriber or observer start listening. An Observable may emit n number of items. It will terminate with success or with an error. For example an object of User who emits User object with a new username, when the new username is set.

The second construct is Subscriber or Observer.

Subscriber: Subscriber basically listens to those events emitted by observable. An observable may have any number of subscribers.

The third construct is Schedulers.

Schedulers: Another super huge advantage with RxJava is Instance concurrency. The way RxJava does that is with Schedulers. For example, We say hey you have this observable and this observer when you established this connection, basically do it in this particular thread.

Creating Observable

Various types of creating Observable.

Single: You subscribe to a single either you get back value or an error.

*Either succeeds with an item or error.

* No BackPressure support.

* Think “Reactive scalar”.

Completable: It is a set of code you can run then it may complete successfully or fail.

* Completable similar to a method that return type is void.

* Either completes or errors or has no items.

* No BackPressure support.

* Think “Reactive runnable”.

Maybe: This either has an item error or potentially has no items.

* No BackPressure support.

* Think “Reactive optional”.

Flowable: This either emits 0 item or emits n items terminates with success or with an error event.

* Have BackPressure support.

* Can emit any number of items.

Observable: This either emits 0 item or emits n items terminates with success or with an error event.

* No BackPressure Support.

* Can emit any number of items.

Observables emit table.

Emit SourceReactive StreamNo BackPressure
o..n items,complete,errorFlowableObservable
item,complete,errorMaybe
item,errorSingle
complete,errorCompletable

So, enough of this theory, let’s dive into coding and see how we can create observables.

Before start please add RxJava dependency.

For Java

<dependency>
    <groupId>io.reactivex.rxjava2</groupId>
    <artifactId>rxjava</artifactId>
    <version>2.0.4</version>
</dependency>

Dependency on Android.

compile 'io.reactivex.rxjava2:rxjava:2.0.8'

The following shows an example how we can create simple observable.

Observable<String> helloWorldObservable = Observable.just("Hello World");

RxJava provides so many static methods for creating observables. Just is one of the static methods for creating observable. Just is basically saying give me the observable of hello string. You can pass any object in Just method instead of string to create observable. So, this our first construct for creating observable.

The following shows how we can subscribe to observable.

helloWorldObservable.subscribe(new DefaultObserver<String>() {
            @Override
            public void onNext(String s) {
               System.out.println(s);
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {

            }
        });

When we called subscribe on observable, observable start emitting item. You see subscribe method accepts Observer interface as a parameter. The onNext() method is called when observable emit new item. The onError() method is called when an error is occurred in emitting. The onComplete() method is called when observable finishes its data flow. So, this is our second construct.

The following show previous example with lambdas.

helloWorldObservable.subscribe(s -> System.out.println(s), throwable -> throwable.printStackTrace(), () -> System.out.println("Emittion completes"));

Let’s see another example of creating observable.

String strings[] = new String[]{"Hello", "World"};     // Array of resource
Observable.fromArray(strings).subscribe(s -> System.out.println(s),throwable -> throwable.printStackTrace(),() -> System.out.println("Emittion completed"));

FromArray is another static method for creating observable. The  FromArray method takes the array of objects and returns the array of object of observable. Now every time onNext() method called, it received a single string value from the array.

The following example shows how you can apply logic before actually receiving the stream.

List<Integer> intergerList = Arrays.asList(54,12,10,78,69,33,66,99,84);
Observable.fromIterable(intergerList)
          .filter(i -> i % 2 == 0)
          .sorted()
          .subscribe(i -> System.out.println(i), throwable -> throwable.printStackTrace(), () -> System.out.println("Emittion completes"));

// Output
10
12
54
66
78
84

FromIterable is another static method for creating observable. The FromIterable method takes the list of objects as a parameter and returns the list of object of observable. Another thing of noticeable here is the chaining of observables. You see every operator returns an observable and we can chain them. The filter method takes the Predicate interface and performs the logic on it. The sorted method sorts the result in ascending order. You can also pass the custom Comparator interface for sorting.

The following example shows how you can merge the result of two observable into one. Another cool feature of a startWith method.

static class Person {

        enum SEX {
            MALE, FEMALE, SHE_MALE
        }

        private String name;
        private int age;
        SEX sex;

        Person(String name, int age, SEX sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
}    This Person class we are going to use in our example.

// Create observable code.
Observable<Person> firstObservable = Observable.create(emitter -> emitter.onNext(new Person("Shayan Tahir", 22, Person.SEX.MALE)));
Observable<Person> secondObservable = Observable.create(emitter -> emitter.onNext(new Person("Bilal Ahmed", 25, Person.SEX.MALE)));
secondObservable.mergeWith(firstObservable)
                .startWith(new Person("Jon Doe", 20, Person.SEX.FEMALE))
                .subscribe(person -> System.out.println(person.name)
                        , Throwable::printStackTrace,() -> System.out.println("Emittion complete"));

// Output

// Jon Doe
// Bilal Ahmed
// Shayan Tahir

Create is another static method for creating observable. The Create method accepts ObservableOnSubscribe interface for creating observable. With Create method we have the ability to call onNext multiple times. Now with merge method, we can merge the output of two observable into one. Another interesting method is startWith. The startWith method returns an Observable that emits a specified item before emitting the sources.

RxJava provides several other methods for creating observable.

Observable.fromCallable(): FromCallable essentially modeling some synchronous behavior that returns a single value.

Observable.timer(): This function means to execute this task every x seconds until someone unsubscribes it.

Observable.interval(): Execute this function every x seconds without delay and stops when someone unsubscribes it.

Observable.concat(): Concatenates the elements of each observable provided by the observables as a parameter.

Note: Similar methods exist in all observable types. Examples Flowable, Maybe, Completeable and Single.

Now, you guy’s must be thinking where is the asynchronous code, how we can handle multithreading with this. Let me tell you what we do before these all are the basics of RxJava how to create observables. Now we’re going to see the real power of RxJava.

The following is the example of how we can create a timer task with observable.

Disposable disposable = Observable.timer(5000, TimeUnit.MILLISECONDS)
                .subscribeOn(Schedulers.io())
                .subscribe(aLong -> {
                              doLongRunningTask();
                        }
                        , Throwable::printStackTrace,
                        () -> System.out.println("Observable complete"));

This piece of code runs after every five seconds and do some long running task. SubscribeOn is the method in which we tell do the task to run in a background thread by giving Schedulers object.

The following example shows how we can make a network request with observable.

Observable<User> userObservable = Observable.fromCallable(() -> client.getUser(request.execute()));

This is how you can make a network request with observables. The client is the network interface. But we have a problem here, that network request still going to be done synchronously. So, what do we do to make it asynchronous?

The following example shows how we can make network request asynchronously with observable.

Observable.fromCallable(() -> client.getUser(request.execute()))
                .subscribeOn(Schedulers.io())
                .subscribe(user -> {
                    // performOperationWithUser(user);
                });

So, we apply an operator that changes the thread with background thread when we subscribe to the observable. The subscribeOn is the operator that changes the current thread with a background thread and accepts a Schedulers object as a parameter.

The following example shows how you can make two asynchronous network request with dependency. The second request depends on first request response.

Observable<User> tempObservable = Observable.fromCallable(() -> client.getUser(request.execute()))
        .subscribeOn(Schedulers.io());

tempObservable
        .map(user -> user.getId())
        .flatMap(id -> Observable.fromCallable(() ->
                client.getUserSettings(request.execute(id)))
                .subscribeOn(Schedulers.io()))
        .subscribe(userSettings -> {
               showUserSettings()
        }, throwable -> throwable.printStackTrace(), () -> System.out.println("Request completes"));

At first, we simply make a getUser network request. With first observable when we get a User object, we make another request for fetching UserSettings request. In the previous example, we have a map and flatMap operator, map operator provides you a function that it basically returns a different object and flatMap operator basically accepts an object and return a new observable. Finally, we get the userSettings object in subscribe method.

Below example shows how you can make two network request that is independent of each other.

Observable.merge(Observable.fromCallable(() -> client.updateUser(request.execute(user)))
                .subscribeOn(Schedulers.io()),
        Observable.fromCallable(() -> client.addNewUserComment(request.execute(user.getId)))
                .subscribeOn(Schedulers.io()))
        .subscribe(response -> System.out.println("Data inserted in to database.")
                , Throwable::printStackTrace);

Observable.merge is the static method for creating observable. In here it basically executes two network request simultaneously, when both request complete with success it will be called onNext, if it got an error it will be called the onError method.

RxJava provides many methods for converting the observable into other types.

Conversion of Observable.

From / ToFlowableObservableMaybeSingleCompleteable
FlowabletoObservable()elementAt()
reduce()
firstElement()
lastElement()
singleElement()
scan()
elementAt()
first()
firstOrError()
single()
singleOrError()
last()
lastOrError()
all()
any()
count()
ignoreElements()
ObservabletoFlowable()elementAt()
reduce()
firstElement()
lastElement()
singleElement()
scan()
elementAt()
first()
firstOrError()
single()
singleOrError()
last()
lastOrError()
all()
any()
ignoreElements()
MaybetoFlowable()toObservable()toSingle()
seruenceEqual()
toCompleteable()
SingletoFlowable()toObservable()toMaybe()toCompleteable()
CompleteabletoFlowable()toObservable()toMaybe()toSingle()
toSingleDefault()

Now it’s time to see how RxJava helps us to use this in the making of Android App. For this, we have to add another dependency to our build.gradle file.

// Rx java dependency
implementation 'io.reactivex.rxjava2:rxjava:2.1.8'

// Rx Android dependency
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'          // change version number with latest version.

RxAndroid

RxAndroid is an extension of RxJava. It provides feasibility to run code in the Main Thread of Android from Background Thread. RxAndroid is specifically for Android.

Let’s see a simple example of how to make a network request in a Background Thread and retrieving the response in Main Thread. For network request, we are using another popular library Retrofit. Now why I’m saying you this, because retrofit gives you this option of converting it automatically to an observable.

Below is the simple interface of retrofit with observable.

public interface ServiceUtil{
    
     @GET("user")
     Observable<User> getUser();
}

This is a basic interface of  Retrofit. If you want to see the example of how to create Retrofit and the interface properly, please see this example.

Now we just need to call this function in our activity.

disposable = serviceUtil.getUser()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(user -> {
                showUser(user);
        }, Throwable::printStackTrace, () -> System.out.println("Fetch User Successfully"));

You see in here when we subscribing to observable. We’re telling that execute our network request in a background thread. Another advantage of RxJava we can specify in which thread we want our response. So in Android, we only update views from Main Thread. That’s why we’re telling observed on in Main Thread. 

Subscribe method returns a disposable object. It’s a good approach that we dispose of it in onStop or onDestroy method, because of memory leak.

Below is the example how we can dispose of.

protected void onStop(){
    super.onStop();
    if(disposable != null && !disposable.isDisposed()){
          disposable.dispose();
    } 
}

So, this is my demonstration about RxJava, although RxJava is much more than this I explain. I encourage you to read more about RxJava on their official website.

If you guys want to see how to work RxJava, Dagger and Retrofit see this example of my blog. Also, you can see complete example here on GitHub.

If you’ve any queries, please do comment below.

Thank you for being here.

This is the third part of Dagger with Retrofit tutorial. In this part, we’re going to make our network request with RxJava and simply show data in RecyclerView.

Note: I strongly recommend you to see my previous blogs this series. If you haven’t seen my previous blogs, then I’m afraid you will not understand.

So, without further ado let’s dive into coding.

Below are the libraries we need to add to our build. Gradle file to make a network request with RxJava and also to work with the retrofit.

// Rx java dependency
implementation 'io.reactivex.rxjava2:rxjava:2.1.8' // please add current version of dependencies.

// Rx Android dependency
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' 

// Rx java adapter version
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'

Now we need little more improvement in ServiceUtilModule to tell the retrofit builder that we want you to enable RxJava in a network request. By the way, ServiceUtilModule is a part of the dagger which I discussed in the first tutorial.

Below is the code for creating retrofit with RxJava Adapter which is in ServiceUtilModule.

@Provides
@CustomApplicationScope
Retrofit getRetrofit(OkHttpClient okHttpClient, Gson gson) {
    return new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava Adapter Factory instance for network request.
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .build();
}

Here we need to update our ServiceUtil interface. We have to change the return type of our Network Method from Call to Observable.

ServiceUtil

public interface ServiceUtil {

    @GET("GetCarsCategories.php")
    Observable<CarCategoryResponse> getCarCategories();
}

So, you guys holding just want to make sure because interesting part comes in where we are actually going to see the magic of RxJava. In here we need to update our MainActivity code. In MainActivity we are only updating retrofit network request code, remaining code will be pretty much same.

Below is network call with RxJava, and I believe you guys know lambda’s as well.

disposable = serviceUtil.getCarCategories()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(carCategoryResponse -> {
                    if (carCategoryResponse.getCarCategories() != null && carCategoryResponse.getCarCategories().size() > 0) {
                        this.carCategories.addAll(carCategoryResponse.getCarCategories());
                        carCategoryAdapter.notifyDataSetChanged();
                    } else
                        Toast.makeText(this, "No data found!", Toast.LENGTH_SHORT).show();
                }, throwable -> {
                    if (mainProgressBar.getVisibility() == View.VISIBLE)
                        mainProgressBar.setVisibility(View.GONE);
                    Toast.makeText(this, "Internet not connect", Toast.LENGTH_SHORT).show();
                }, () -> {
                    if (mainProgressBar.getVisibility() == View.VISIBLE)
                        mainProgressBar.setVisibility(View.GONE);
                });

I believe many of you really don’t understand what is just happened. So, don’t worry I’m going to explain this line by line, because this is our main topic RxJava right. So, let’s do it.

Below is the first line of calling getCarCategories function.

serviceUtil.getCarCategories()

In here we actually making our network request with RxJava and getting observable as return type nothing fancy.

The following shows the second line of code.

.subscribeOn(Schedulers.io())

The subscribeOn function basically asking in which thread you want to run your network request. So, we are telling that we want to run our network request in a background thread bypassing Schedulers.io. If we do not execute network request in background thread then our app crashes and we get an error by saying NetworkOnMainThreadException.

The following shows the third line of code.

.observeOn(AndroidSchedulers.mainThread())

The observeOn function asking in which thread you want your response back. So, we’re telling that we want our response back in main thread by passing AndroidSchedulers.mainThread. If we do not listen in the main thread then our app crashes and we get an error by saying CalledFromWrongThreadException.

Below is the fourth line code. The first parameter of subscribes function.

subscribe(carCategoryResponse -> {
                    if (carCategoryResponse.getCarCategories() != null && carCategoryResponse.getCarCategories().size() > 0) {
                        this.carCategories.addAll(carCategoryResponse.getCarCategories());
                        carCategoryAdapter.notifyDataSetChanged();
                    } else
                        Toast.makeText(this, "No data found!", Toast.LENGTH_SHORT).show();
                }

The subscribe method in which we are actually getting our response back. Subscribe function accepting Consumer Interface as a parameter. The consumer interface accepting CarCategoryResponse object and with that, we’re performing some basics stuff like setting RecyclerView data.

The second parameter of subscribes function.

throwable -> {
                    if (mainProgressBar.getVisibility() == View.VISIBLE)
                        mainProgressBar.setVisibility(View.GONE);
                    Toast.makeText(this, "Internet not connect", Toast.LENGTH_SHORT).show();
                }

The second parameter of subscribes method accepting consumer interface but here consumer Interface accepting a Throwable. If any error occurred during the network request this interface will be hit. So, that’s why we’re setting visibility gone to our ProgressBar and show some Toast.

The third parameter of subscribes function.

() -> {
                   if (mainProgressBar.getVisibility() == View.VISIBLE)
                       mainProgressBar.setVisibility(View.GONE);
               }

The third parameter of subscribes method accepting Action interface. This will be called if everything goes right. So, in here we just need to hide the ProgressBar.

Note: Action interface will not be called if the error occurred during the network request. It will only call Consumer with accepting Throwable object interface.

Now one last thing remains which is DisposableThe subscribes method return type is Disposable. We can use disposable to dispose of the running task in onStop.

@Override
protected void onStop(){
    if(disposable != null && !disposable.isDisposed())
        disposable.dispose();
}

This is how we can stop our currently running tasks with disposable in RxJava.

So, this is it our example with Dagger and RxJava is complete. I hope you guys have a good understanding of how to make a network request with RxJava

Download Complete Code

If you’ve any queries regarding this post, please do comment below.

Thank you for being here and keep reading…

Previous Part

This is the second part of Dagger with Retrofit tutorial. In the first part, we’ve achieved the hard part of how to set up dependency injection. In this article, we’re gonna see how we can use the dependencies in our Activity.

So, let’s begin with adding Dagger to our Activity. Below is the MainActivity.

@Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       ServiceApplicationComponent component = DaggerServiceApplicationComponent.builder()
               .applicationContextModule(new ApplicationContextModule(this))
               .build()
       component.inject(this);
       initViews();
       setRecyclerViewProperties();
       carCategoriesCallback = serviceUtil.getCarCategories();
   }

The interesting part of this code is DaggerApplicationComponentBuilder. You see dagger automatically create a builder pattern for our Component class. In our builder class, we are providing a new instance of ApplicationContextModule because dagger restricted us to provide an instance of that Module which has a constructor parameter.

Dagger provide a way for the fields in your activity, fragments or services to be assigned references simply by annotating the field with an @Inject. After injecting the field dagger2 locate the singletons in the dependency graph to try to find the matching return type. Below is an example of how to @Inject annotation in the field.

@Inject
Picasso picasso;
@Inject
ServiceUtil serviceUtil;

This is how you can @inject your fields into activity at the top. Now, lets’ move how you can enqueue your network request with the retrofit.

carCategoriesCallback.enqueue(this);

In enqueue function this represents callback, and for that our MainActivity has to implement callback. One more thing by doing enqueue you are using synchronous call. The synchronous method provides the ability to use return value directly because the operation blocks everything else during the network request.

Note: For non-blocking UI you have to handle the request execution in the separate thread.

Below is the code for retrofit callback interface methods.

@Override
public void onResponse(@NonNull Call<CarCategoryResponse> call, @NonNull Response<CarCategoryResponse> response) {
    if (mainProgressBar.getVisibility() == View.VISIBLE)
        mainProgressBar.setVisibility(View.GONE);
    if (response.isSuccessful()) {
        if (response.body().getCarCategories() != null && response.body().getCarCategories().size() > 0) {
            this.carCategories.addAll(response.body().getCarCategories());
            carCategoryAdapter.notifyDataSetChanged();
        } else 
            Toast.makeText(this, "No data found!", Toast.LENGTH_SHORT).show();        
    } else 
        Toast.makeText(this, "Communication error internet not connect!", Toast.LENGTH_SHORT).show();    
}

@Override
public void onFailure(@NonNull Call<CarCategoryResponse> call, @NonNull Throwable t) {
    if (mainProgressBar.getVisibility() == View.VISIBLE)
        mainProgressBar.setVisibility(View.GONE);
    Toast.makeText(this, "Communication error internet not connect!", Toast.LENGTH_SHORT).show();
}

If the call succeeds the callback will come into the onResponse method. If any error occurs in the request callback will go into the onFailure method. Now in the onResponse method, we need to add data in the  RecyclerView Adapter.

RecyclerViewAdapter

public class CarCategoryAdapter extends RecyclerView.Adapter<CarCategoryViewHolder> {

    private List<CarCategoryResponse.CarCategory> carCategories;
    private final LayoutInflater layoutInflater;

    public CarCategoryAdapter(List<CarCategoryResponse.CarCategory> carCategories, final Context context) {
        this.carCategories = carCategories;
        this.layoutInflater = LayoutInflater.from(context);
    }

    @NonNull
    @Override
    public CarCategoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new CarCategoryViewHolder(layoutInflater.inflate(R.layout.list_item_single_view, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull CarCategoryViewHolder holder, int position) {
        CarCategoryResponse.CarCategory carCategory = carCategories.get(position);
        holder.getBase_fare().setText(String.valueOf("Rs ").concat(carCategory.getBaseFare()));
        holder.getCar_category().setText(carCategory.getName());
        holder.getRate_per_km().setText(String.valueOf("Rs ").concat(carCategory.getRatePerKm()));
        holder.getRate_per_min().setText(String.valueOf("Rs ").concat(carCategory.getRatePerMin()));
    }

    @Override
    public int getItemCount() {
        return carCategories.size();
    }
}

The Adapter provides a binding for an app specific data set to views that are displayed in a RecyclerView. In the onBindViewHolderwe update the contents of itemView to reflect the item at the given position.

RecyclerViewHolder

public class CarCategoryViewHolder extends RecyclerView.ViewHolder {

    private TextView car_category, rate_per_min, rate_per_km, base_fare;

    public CarCategoryViewHolder(View itemView) {
        super(itemView);
        car_category = itemView.findViewById(R.id.car_category);
        rate_per_min = itemView.findViewById(R.id.rate_per_min);
        rate_per_km = itemView.findViewById(R.id.rate_per_km);
        base_fare = itemView.findViewById(R.id.base_fare);
    }

    public TextView getCar_category() {
        return car_category;
    }

    public TextView getRate_per_min() {
        return rate_per_min;
    }

    public TextView getRate_per_km() {
        return rate_per_km;
    }

    public TextView getBase_fare() {
        return base_fare;
    }
}

The ViewHolder describes an itemView and metadata about its place within a RecyclerView. In CarCategoryViewHolder we’re simply finding the views and set it to its relevant field.

CarCategoryResponse

public class CarCategoryResponse {

    @SerializedName("car_categories")
    @Expose
    private List<CarCategory> carCategories;

    public List<CarCategory> getCarCategories() {
        return carCategories;
    }

    public class CarCategory {

        @SerializedName("id")
        @Expose
        private int id;
        @SerializedName("name")
        @Expose
        private String name;
        @SerializedName("rate_per_km")
        @Expose
        private String ratePerKm;
        @SerializedName("rate_per_min")
        @Expose
        private String ratePerMin;
        @SerializedName("base_fare")
        @Expose
        private String baseFare;

        @Override
        public String toString() {
            return "CarCategory{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", ratePerKm=" + ratePerKm +
                    ", ratePerMin='" + ratePerMin + '\'' +
                    ", baseFare='" + baseFare + '\'' +
                    '}';
        }

        public int getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getRatePerKm() {
            return ratePerKm;
        }

        public String getRatePerMin() {
            return ratePerMin;
        }

        public String getBaseFare() {
            return baseFare;
        }
    }
}

Alright, this was the complete example of a dagger with the retrofit. If you guys want to learn how to make a network call with retrofit and Rx-Java check out the next article.

I hope this blog gives you a good understanding of the dagger and retrofit. If you’ve any queries regarding this please do comment below.

Thank you for being here and keep reading…

 

Today, we’re going to learn about Dagger 2. Dagger 2, first of all, is really an annotation processor or just the code generator. Many of the people seem to think that Dagger 2 is how you do dependency injection, actually, it’s not. It is just a boilerplate code generator and dependency injection is something that you code yourself.

Dagger use these following annotations.

  1. @Module and @Provide: Module classes are basically there for providing an object which can be injected in future.
  2. @Component: Component is an interface and knows everything about object creation. Every object we want to use delivered by the component in some way.
  3. @Inject: Inject can be used on a constructor, object and on a field.
  4. @Qualifier: A Qualifier is an annotation tells you the difference between the two objects.

So, without further ado, we are going to start our example in which we learn how to work with Dagger2. In this example, we simply hit a URL and download some JSON text and display them into RecyclerView.

Android App Setup

For the libraries, we are using a sort of standard libraries. Below is the app level build.gradle file.

apply plugin: 'com.android.application'
apply plugin: 'com.jakewharton.butterknife'

android {
    compileSdkVersion 27
    buildToolsVersion "27.0.3"
    defaultConfig {
        applicationId "Change with your project name"
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    } // For to enable java8
}

ext {
    supportVersion = '27.1.0'
    daggerVersion = '2.13'
    retrofitVersion = '2.3.0'
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })

    // Dagger dependencies
    implementation "com.google.dagger:dagger-android:$daggerVersion"
    implementation "com.google.dagger:dagger:$daggerVersion"
    implementation "com.google.dagger:dagger-android-support:$daggerVersion"
    annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
    annotationProcessor "com.google.dagger:dagger-android-processor:$daggerVersion"

    // Timber dependency
    implementation'com.jakewharton.timber:timber:4.6.0'

    // Retrofit dependencies
    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"

    // OkHttp3 dependencies
    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
    implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'

    // Picasso dependency
    implementation 'com.squareup.picasso:picasso:2.5.2'

    // Android support dependencies
    implementation "com.android.support:appcompat-v7:$supportVersion"
    implementation "com.android.support:recyclerview-v7:$supportVersion"
    implementation "com.android.support:cardview-v7:$supportVersion"
    implementation "com.android.support:design:$supportVersion"
    implementation "com.android.support:support-v4:$supportVersion"
    implementation "com.android.support:animated-vector-drawable:$supportVersion"

    // Ui constraint dependency
    implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta6'

    testImplementation 'junit:junit:4.12'
}

All the dependencies which we require through the app are written in components.

Main App level Component.

@CustomApplicationScope
@Component(modules = {ServiceUtilModule.class, PicassoModule.class})
public interface ServiceApplicationComponent {

    Picasso getPicasso();

    ServiceUtil getServiceUtil();  
}

We are telling our component in order to generate these instances you need ServiceUtilModule.class and PicasoModule.class.

You see all the internal stuff should be kept internally. We don’t want that stuff flying off the sunset. We don’t want people to use our dependency graph to know about the how do we make Picasso and ServiceUtil Object. It should be automated and under the hood.

Now we need to provide Picasso and ServiceUtil instance where does he get that from because dagger can’t magically generate all this stuff. This is where Module comes to play.

One last thing you guys noticed in code is a CustomApplicationScope annotation. Scope means any instance we got of that object should be a single instance. This is what a scope does.

CustomApplicationScope

@Scope
@Retention(RetentionPolicy.CLASS)
public @interface CustomApplicationScope {
}

Now in which object, class or method you include @CustomApplicationScope that means you need a single instance of that object throughout the app.

ServiceUtilModule

@Module(includes = NetworkModule.class)
public class ServiceUtilModule {

    private static final String BASE_URL = "https://www.frisbeego.com/secure/index.php/";

    @Provides
    @CustomApplicationScope
    ServiceUtil getServiceUtil(Retrofit retrofit) {   // How does the retrofit object came as a parameter to this function.You see @Provides annotation on every method this means it providing that object.
        return retrofit.create(ServiceUtil.class);
    }

    @Provides
    @CustomApplicationScope
    Gson getGson() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        return gsonBuilder.create();
    }

    @Provides
    @CustomApplicationScope
    Retrofit getRetrofit(OkHttpClient okHttpClient, Gson gson) {
        return new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .baseUrl(BASE_URL)
                .client(okHttpClient)
                .build();
    }
}

Now our ServiceUtilModule class also depends on NetworkModule and for that we need NetworkModule.

NetworkModule

@Module(includes = ApplicationContextModule.class)
public class NetworkModule {

    @Provides
    @CustomApplicationScope
    HttpLoggingInterceptor getInterceptor() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(message -> Timber.i(message));
        interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
        return interceptor;
    }

    @Provides
    @CustomApplicationScope
    Cache getCache(File cacheFile) {
        return new Cache(cacheFile, 10 * 1000 * 1000);  // 10 MiB cache
    }

    @Provides
    @CustomApplicationScope
    File getFile(@ApplicationContextQualifier Context context) {
        File file = new File(context.getFilesDir(), "cache_dir");
        if (!file.exists())
            file.mkdirs();
        return file;
    }

    @Provides
    @CustomApplicationScope
    OkHttpClient getOkHttpClient(HttpLoggingInterceptor interceptor, Cache cache) {
        return new OkHttpClient.Builder()
                .writeTimeout(15, TimeUnit.SECONDS)
                .readTimeout(15, TimeUnit.SECONDS)
                .connectTimeout(15, TimeUnit.SECONDS)
                .cache(cache)
                .addInterceptor(interceptor)
                .build();
    }
}

NetworkModule also depends on ApplicationContextModule class. Now you guys are getting the clear picture that our graph of dependencies are getting bigger and bigger and we need to structure it.

ApplicationContextModule

@Module
public class ApplicationContextModule {

    private final Context context;

    public ApplicationContextModule(Context context) {
        this.context = context.getApplicationContext();
    }

    @Provides
    @CustomApplicationScope
    @ApplicationContextQualifier
    Context getContext() {
        return context;
    }
}

You see in here we have a new annotation called @AppilcationContextQualifier. Now why we need @Qualifer here because in an android app there are two types of context that’s why we need to specify which context we are providing.

ApplicationContextQualifier

@Qualifier
public @interface ApplicationContextQualifier {

}

Here’s everything is done for ServiceUtilModule.

PicassoModule

@Module(includes = NetworkModule.class)
public class PicassoModule {

    @Provides
    @CustomApplicationScope
    Picasso getPicasso(@ApplicationContextQualifier Context context, OkHttp3Downloader okHttp3Downloader) {
        return new Picasso.Builder(context)
                .downloader(okHttp3Downloader)
                .build();
    }

    @Provides
    @CustomApplicationScope
    OkHttp3Downloader getOkHttp3Downloader(OkHttpClient okHttpClient) {
        return new OkHttp3Downloader(okHttpClient);
    }
}

You see in here PicassoModule also depends on NetworkModule. Here we are going to reuse our NetworkModule for PicassoModule which we previously use in ServiceUtilModule.

This was all for the dependency injection. Let’s clap your hand with joy because you guys have learned something which people uses in the huge application.

Alright, guys, I’m going to end this blog here, for further reading see my next blog.

I hope this blog gives you a good understanding of Dagger. If you’ve any queries regarding this post please do comment below.

Thank you for being here and keep reading…

Next Part