Skip to content

Developer Guide

Gayashan Bombuwala edited this page Nov 6, 2018 · 4 revisions

Welcome to the Bassa-mobile Developer Guide

Configuring the Bassa-backend

Bassa-mobile directly depends on the Bassa-backend and you can't go beyond the Sign-In screen, if you haven't configured it properly.

Go through the following steps to configure it.

  • Navigate to the ./app/constants folder from the root folder and open the API.js file.
  • Replace the values of the following properties according to the configurations of your Bassa-backend.
const APIConstants = {`
  `HOST_URL: 'http://10.0.3.2',`
  `HOST_PORT: 5000,`
  `KEY: '123456789',`
`};

** HOST_URL property can contain one of the following 3 values, depending on your device.**

Genymotion

10.0.3.2

Android Studio Emulator

10.0.2.2

External device

obtain the IP of your computer using the ifconfig command on Unix-like OS or ipconfig command on Windows. (Computer and the device should be on the same network)

Directory structure

  • ./app/actions/ - Redux actions that change the Redux store
  • ./app/components/ - Components that does't get connected with the Redux store (i.e. ActivityLoader component).
  • ./app/constants/ - Reusable constants related to the project (i.e. API constants).
  • ./app/containers/ - Containers/Components that gets connected with the Redux store (i.e. QuotaUsage component).
  • ./app/containers/CustomDrawer.js - Drawer component used in the app.
  • ./app/helpers/ - Helper functions related to the project such as API request interceptors and common utility funtions
  • ./app/images/ - Static image files related to the project (i.e. App logo).
  • ./app/reducers/ - Redux reducers that handle state changes
  • ./app/containers/RootNavigator.js - Navigation routes used in the app.
  • ./app/sagas/ - Redux sagas that perform async actions in the app (i.e. Setting the auth token to the API).
  • ./app/services/ - API calls that return Promises (i.e. signIn).
  • ./app/store/ - Redux store configurations.
  • ./app/styles/ - Common styles used in the app (i.e. Status bar color).
  • ./__tests__/ - Unit testing files.

Example Development Workflow: 1 - Sign In (by @gayashanbc)

Let's see how we can use the above mentioned directory structure, in order to develop the Sign-In workflow.

  1. Implement the draft Sign-In container and connect to the Redux store.
...
class SignIn {
...
}
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
...
  1. Define the navigation routes.
...
const AppNavigator = createStackNavigator({
...
  SignIn: { screen: SignIn },
...
});
...
  1. Define the constants required for action types.
export const userActions = {
  USER_SIGN_IN: 'USER_SIGN_IN',
  AUTHENTICATE_USER_SUCCESS: 'AUTHENTICATE_USER_SUCCESS',
  AUTHENTICATE_USER_FAIL: 'AUTHENTICATE_USER_FAIL',
};
  1. Define the Redux actions.
export const signIn = (username, password) => ({
  type: userActions.USER_SIGN_IN,
  payload: { user_name: username, password },
});
  1. Define the Redux reducer.
...
const initialState = {
  currentUser: {
    username: '',
    isAdmin: false,
    timestamp: null,
  },
};

function userReducer(state = initialState, action) {
  switch (action.type) {
    case userActions.AUTHENTICATE_USER_SUCCESS:
      return {
        ...state,
        currentUser: {
          ...state.currentUser,
          username: action.payload.username,
          isAdmin: action.payload.isAdmin,
          timestamp: action.payload.timestamp ? action.payload.timestamp : Date.now(),
        },
      };
    default:
      return state;
  }
}

export default userReducer;
  1. Define the required API calls as a service.
import APIBuilder from '../helpers/APIBuilder';
import { prepareRequestBody } from '../helpers/utils';

const signIn = credentials => APIBuilder.API.post('/api/login', prepareRequestBody(credentials));
  1. Define a Redux Saga to handle async operations involved in the Sign-In workflow.
...
function* signInUser({ payload }) {
  try {
    const response = yield call(UserService.signIn, payload);
    if (response.status === 200) {
      yield call(KeychainService.setGenericPassword, payload.user_name, payload.password);
      yield put(handleAuthSuccess({
        username: payload.user_name,
        isAdmin: Number(response.data.auth) === 0 ? true : false,
      }));
      yield put(setTokenToHeader(response.headers.token));
      yield put(resetToMainDrawer());
    } 
  } 
...
}
...
export default function* userSaga() {
  yield all([
    takeEvery(userActions.USER_SIGN_IN, signInUser),
  ]);
}
  1. Complete the implementation of the SignIn container (component) with the help of the above defined artifacts.
  2. Write tests to validate the new code (i.e Snapshot testing, Unit testing).
...
describe('User Reducer', () => {
    it('should return the initial state', () => {
        expect(userReducer(undefined, {})).toEqual(
            {
                currentUser: {
                    username: '',
                    isAdmin: false,
                    timestamp: null,
                },
            }
        )
    });
...

Example Development Workflow:2 - Sign Up (by @TheLukaszNs)

Let's see how we can use the Bassa-mobile project structure (check it out there), to develop the Sign-Up workflow.

  1. Implement the Sign-Up Container and connect it to the Redux store.
class SignUp {
  ...
};
...
export default connect(mapStateToProps, mapDispatchToProps)(SignUp);
  1. Define the navigation rules.
const AppNavigator = createStackNavigator({
  SignUp: { screen: SignUp },
  ...
});
  1. Define the types required by actions and reducer.
export const userActions = {
  USER_SIGN_UP: 'USER_SIGN_UP',
  SIGN_UP_USER_SUCCESS: 'SIGN_UP_USER_SUCCESS',
  SIGN_UP_USER_FAIL: 'SIGN_UP_USER_FAIL',
}
  1. Define the Redux actions.
export const signUp = (username, email, password, confirmPassword) => ({
  type: userActions.USER_SIGN_UP,
  payload: {
    user_name: username, email, password, confirm_password: confirmPassword,
  },
});
  1. Define the Redux reducer.
// In this particular example, we don't need the reducer
  1. Add newly created reducer to the root reducer
const rootReducer = combineReducers({
  // The new reducer would have gone here
  ...
});

export default rootReducer;
  1. Define the required API calls as a service.
import APIBuilder from '../helpers/APIBuilder';

const signUp = userData => APIBuilder.API.post('/api/regularuser', JSON.stringify(userData));
  1. Define a Redux Saga to handle async operations involved in the Sign-Up workflow.
...
function* signUpUser({ payload }) {
  try {
    const response = yield call(UserService.signUp, payload);
    if (response.status === 200) {
      yield put(handleSignUpSuccess(response.data));
      Alert.alert('Success', 'Please note that your account needs to be approved by an Admin before you Sign-In');
      yield put(resetToSignIn());
    } else {
      Alert.alert('Error', 'Sign-Up failed!');
      yield put(handleSignUpFailure(response.problem));
      yield put(resetToSignIn());
    }
  } catch (error) {
    Alert.alert('Error', 'Sign-Up failed!');
    yield put(handleSignUpFailure(error));
    yield put(resetToSignIn());
  }
}

export default function* userSaga() {
  yield all([
    takeEvery(userActions.USER_SIGN_UP, signUpUser),
  ]);
}
  1. Write tests to validate the new code
...
describe('User Actions', () => {
  ...
    test('Should create an action to SignUp', () => {
        const username = 'rand';
        const email = 'gayashanbc@gmail.com';
        const password = 'pass';
        const confirmPassword = 'pass';
        const expectedAction = {
            type: userActions.USER_SIGN_UP,
            payload: { user_name: username, password, email, confirm_password: confirmPassword }
        };
        expect(signUp(username, email, password, confirmPassword)).toEqual(expectedAction);
    });
  ...
});
...

Example Development Workflow: 3 - Add new download (by @jmularski)

Let's see how we can use the Bassa-mobile project structure (check it out there), to develop the add new download workflow.

  1. Implement the draft Downloads Container and connect it to the Redux store.
class InProgressDownloads {
  ...
};
...
export default connect(mapStateToProps, mapDispatchToProps)(InProgressDownloads);
  1. Define navigation
export const DownloadsTabs = createMaterialTopTabNavigator({
    InProgressDownloads: { screen: InProgressDownloads },
    ...
});
...
const Drawer = createDrawerNavigator({
      Downloads: { screen: DownloadsTabs },
  },
...
);
...
const AppNavigator = createStackNavigator({
    MainDrawer: { screen: Drawer, navigationOptions: { header: null } },
    ...
});
  1. Check if user download link is valid.
...
isValidLink(link) {
    const urlvalidatorRegex = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/i;
    const magnetvalidatorRegex = /magnet:\?xt=/i;
    if (urlvalidatorRegex.test(link) || link.match(magnetvalidatorRegex) !== null) {
      return true;
    }
    return false;
}
...
  1. Define the API calls as a Service
import APIBuilder from '../helpers/APIBuilder';
...
const addDownload = downloadLink => APIBuilder.API.post('/api/download', JSON.stringify(downloadLink));
...
  1. Import service and call it in your code
import DownloadService from '../../services/downloadService';
...
await DownloadService.addDownload({ link });
  1. Write tests to validate the new code.

Example Development Workflow:4 - Fetch Completed Downloads (by @rohancl)

Let's see how we can use the Bassa-mobile project structure (check it out there), to develop the workflow for fetching completed downloads.

  1. Implement the draft Completed Downloads Container and connect it to the Redux store.
class CompletedDownloads {
  ...
};
...
export default connect(mapStateToProps)(CompletedDownloads);
  1. Define navigation
export const DownloadsTabs = createMaterialTopTabNavigator({
      ...
      CompletedDownloads: { screen: CompletedDownloads },
});
...
const Drawer = createDrawerNavigator({
      Downloads: { screen: DownloadsTabs },
  },
...
);
...
const AppNavigator = createStackNavigator({
    MainDrawer: { screen: Drawer, navigationOptions: { header: null } },
    ...
});
  1. Define the downloads reducer.
...
const initialState = {
  lastDownloadTimestamp: null,
};

function downloadsReducer(state = initialState, action) {
  switch (action.type) {
    case downloadsActions.UPDATE_LAST_DOWNLOAD_TIMESTAMP:
      return { ...state, lastDownloadTimestamp: Date.now() };
    default:
      return state;
  }
}

export default downloadsReducer;
  1. Define the API call as a Service
import APIBuilder from '../helpers/APIBuilder';
...
const getAllDownloads = () => APIBuilder.API.get('/api/downloads/1');
...
  1. Fetch the downloads asynchronously
...
async fetchDownloads() {
    this.setState({ isRefreshing: true });
    try {
      const response = await DownloadService.getAllDownloads();
      this.setState({ downloadsList: response.data, isRefreshing: false });
    } catch (error) {
      Alert.alert('Error', 'An error occured while fetching completed downloads');
    }
}
...
  1. Show the fetched downloads with intrinsic details such name, timeago,size etc
const CompletedDownloadsRow = ({ item }) => {
  return (
    <View style={styles.rowFront}>
      <View style={styles.rowContainer}>
        <View style={styles.innerRowContainer}>
          <Text style={styles.downloadText}>{item.download_name.length > 30 ? `${item.download_name.substring(0, 25)}...` : item.download_name}</Text>
          <Text style={styles.timesAgoText}><TimeAgo time={moment.unix(item.completed_time)} /></Text>
        </View>
        <Text style={styles.usernameText}>By {item.user_name}</Text>
        <Text style={styles.sizeText}>{formatBytes(Number(item.size))}</Text>
      </View>
    </View>
  );
};
  1. Write tests to validate the new code.