Skip to content

Instantly share code, notes, and snippets.

@OrenBochman
Last active September 9, 2018 15:04
Show Gist options
  • Save OrenBochman/68d9d6825388324cc36be8096397dff4 to your computer and use it in GitHub Desktop.
Save OrenBochman/68d9d6825388324cc36be8096397dff4 to your computer and use it in GitHub Desktop.
Android Master Detail

Advanced Fragment Example - Master/Detail with a Strategy Factory

Implementing Master/Detail with:

  • adaptive master_detail with a handset breakpoint
  • a controller factory.
  • Interface based contract between fragment/activity/controller whie keeping them decoupled

    • main activity with implements the intererface for
<?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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.avi.advancedfragmentexample.activities.MainActivity">
<fragment
android:id="@+id/fragment"
android:name="com.example.avi.advancedfragmentexample.fragments.Fragment1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
package com.example.avi.advancedfragmentexample.activities;
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import com.example.avi.advancedfragmentexample.controllers.ControllersFactory;
import com.example.avi.advancedfragmentexample.R;
import com.example.avi.advancedfragmentexample.fragments.Fragment1;
import com.google.android.gms.maps.model.LatLng;
public class MainActivity extends FragmentActivity implements Fragment1.IUserActions {
private Fragment1.IUserActions userActionsController;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_act);
// Getting a tablet / mobile controller (the factory hides the decision making).
userActionsController = ControllersFactory.createUserInteractionsController(this);
}
// Delegate event handling to the controller
@Override public void onFocusOnLocation(LatLng newLocation){
userActionsController.onFocusOnLocation(newLocation);
}
}
<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="com.example.avi.advancedfragmentexample.fragments.MasterFragment">
<Button
android:id="@+id/button1"
android:text="focus on map"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
/>
</FrameLayout>
package com.example.avi.advancedfragmentexample.fragments;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import com.example.avi.advancedfragmentexample.R;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
public class MasterFragment extends Fragment {
private IUserActions parentActivity;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View fragmentView = inflater.inflate(R.layout.master_frag, container, false);
Button button = (Button) fragmentView.findViewById(R.id.button1);
Log.d("Avi", "Showing GIT before and after, comparison between 2 commits");
// The fragment delegates the click event, to the activity
// Because in this specific excercise, we've decided that the button's behavior context related
// context related = the activity decides (the behavior can change in different activities)
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LatLng newLocation = new LatLng(32.109333, 34.855499);
parentActivity.onFocusOnLocation(newLocation);
}
});
return fragmentView;
}
@Override
// The context is in fact the activity which hosts the fragment
// This function is being called after the activity is being created
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof IUserActions) {
// If the activity which holds the current fragment, obeys to the rules in the
// "contract", defined in the interface ("IUserActions"), then we save a
// reference to the external activity, in order to call it, each time the button
// had been pressed
this.parentActivity = (IUserActions) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement IUserActions");
}
}
@Override
public void onDetach() {
super.onDetach();
parentActivity = null;
}
public interface IUserActions {
public void onFocusOnLocation(LatLng newLocation);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.avi.advancedfragmentexample.fragments.MapFragment">
<fragment
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
package com.example.avi.advancedfragmentexample.fragments;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.avi.advancedfragmentexample.R;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MapFragment extends Fragment implements OnMapReadyCallback, IUserActionsOnMap {
private GoogleMap mMap;
private LatLng currentLocation;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// This section will need refactoring on the long run
LatLng defaultLocation = new LatLng(-33.865143, 151.209900);
this.currentLocation = defaultLocation;
// Exreacrting the intent from the wrapping activity
Intent intent = ((Activity) getContext()).getIntent();
// Initializing the map's location based on a command sent by the previous activity
// This code segment is ONLY relevant to mobile mode, and not tablet mode
String commandName = intent.getStringExtra("commandName");
if (commandName != null && commandName.equals("focusOnLocation")) {
double latitude = intent.getDoubleExtra("latitude", 0);
double longitude = intent.getDoubleExtra("longitude", 0);
this.currentLocation = new LatLng(latitude, longitude);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_fragment2, container, false);
SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
return view;
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
mMap.moveCamera(CameraUpdateFactory.newLatLng(this.currentLocation));
}
@Override
public void onFocusOnLocation(LatLng newLocation) {
this.currentLocation = newLocation;
if (mMap != null) {
mMap.animateCamera(CameraUpdateFactory.newLatLng(newLocation));
mMap.addMarker(new MarkerOptions().position(newLocation));
}
}
}
<?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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.avi.advancedfragmentexample.activities.DetailActivity">
<fragment
android:id="@+id/map"
android:name="com.example.avi.advancedfragmentexample.fragments.MapFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/fragment2"
android:layout_marginEnd="29dp"
android:layout_marginRight="29dp"
tools:layout="@layout/fragment_fragment2" />
</RelativeLayout>
package com.example.avi.advancedfragmentexample.activities;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.example.avi.advancedfragmentexample.R;
import com.example.avi.advancedfragmentexample.fragments.IUserActionsOnMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
public class DetailActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
double latitude = getIntent().getDoubleExtra("latitude", 0);
double longitude = getIntent().getDoubleExtra("longitude", 0);
// SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.fragment2);
// The fragment will read the new arguments and will initialize itself based on them
// savedInstanceState.put.setArguments(bundleArguments);
}
}
package com.example.avi.advancedfragmentexample.fragments;
import com.google.android.gms.maps.model.LatLng;
public interface IUserActionsOnMap {
public void onFocusOnLocation(LatLng location);
}
package com.example.avi.advancedfragmentexample.controllers;
import android.support.v4.app.FragmentActivity;
import com.example.avi.advancedfragmentexample.fragments.Fragment1;
import com.example.avi.advancedfragmentexample.fragments.IUserActionsOnMap;
import com.example.avi.advancedfragmentexample.R;
public class ControllersFactory {
public static Fragment1.IUserActions createUserInteractionsController(FragmentActivity activity) {
// Getting a reference to the maps fragment, ONLY FOUND IN THE TABLET MODE
IUserActionsOnMap mapFragment = (IUserActionsOnMap) activity.getSupportFragmentManager().findFragmentById(R.id.map);
// If the fragment exists --> Tablet mode
if (mapFragment != null) {
return new TabletController(mapFragment);
}
else
{
// Fragment is null --> Mobile mode
return new MobileController(activity);
}
}
}
package com.example.avi.advancedfragmentexample.controllers;
import com.example.avi.advancedfragmentexample.fragments.Fragment1;
import com.example.avi.advancedfragmentexample.fragments.IUserActionsOnMap;
import com.google.android.gms.maps.model.LatLng;
class TabletController implements Fragment1.IUserActions {
private IUserActionsOnMap mapsFragment;
public TabletController(IUserActionsOnMap mapsFragment){
this.mapsFragment = mapsFragment;
}
public void onFocusOnLocation(LatLng newLocation){
mapsFragment.onFocusOnLocation(newLocation);
}
}
package com.example.avi.advancedfragmentexample.controllers;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import com.example.avi.advancedfragmentexample.fragments.Fragment1;
import com.example.avi.advancedfragmentexample.activities.SecondActivity;
import com.google.android.gms.maps.model.LatLng;
class MobileController implements Fragment1.IUserActions {
private Activity activity;
public MobileController(Activity activity){
this.activity = activity;
}
@Override
public void onFocusOnLocation(LatLng newLocation) {
Intent intent = new Intent(activity, SecondActivity.class);
intent.putExtra("commandName", "focusOnLocation");
intent.putExtra("latitude", newLocation.latitude);
intent.putExtra("longitude", newLocation.longitude);
Bundle bundle = new Bundle();
bundle.putDouble("latitude", newLocation.latitude);
bundle.putDouble("longitude", newLocation.longitude);
intent.putExtras(bundle);
activity.startActivity(intent);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment