-
Notifications
You must be signed in to change notification settings - Fork 60
Developer Guide
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 folderfrom therootfolder 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.**
10.0.3.2
10.0.2.2
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)
-
./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.
Let's see how we can use the above mentioned directory structure, in order to develop the Sign-In workflow.
- Implement the draft Sign-In container and connect to the Redux store.
...
class SignIn {
...
}
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
...- Define the navigation routes.
...
const AppNavigator = createStackNavigator({
...
SignIn: { screen: SignIn },
...
});
...- 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',
};- Define the Redux actions.
export const signIn = (username, password) => ({
type: userActions.USER_SIGN_IN,
payload: { user_name: username, password },
});- 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;- 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));- 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),
]);
}- Complete the implementation of the SignIn container (component) with the help of the above defined artifacts.
- 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,
},
}
)
});
...Let's see how we can use the Bassa-mobile project structure (check it out there), to develop the Sign-Up workflow.
- Implement the Sign-Up Container and connect it to the
Reduxstore.
class SignUp {
...
};
...
export default connect(mapStateToProps, mapDispatchToProps)(SignUp);- Define the navigation rules.
const AppNavigator = createStackNavigator({
SignUp: { screen: SignUp },
...
});- 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',
}- Define the
Reduxactions.
export const signUp = (username, email, password, confirmPassword) => ({
type: userActions.USER_SIGN_UP,
payload: {
user_name: username, email, password, confirm_password: confirmPassword,
},
});- Define the
Reduxreducer.
// In this particular example, we don't need the reducer- Add newly created reducer to the root reducer
const rootReducer = combineReducers({
// The new reducer would have gone here
...
});
export default rootReducer;- Define the required API calls as a service.
import APIBuilder from '../helpers/APIBuilder';
const signUp = userData => APIBuilder.API.post('/api/regularuser', JSON.stringify(userData));- Define a
ReduxSaga 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),
]);
}- 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);
});
...
});
...Let's see how we can use the Bassa-mobile project structure (check it out there), to develop the add new download workflow.
- Implement the draft Downloads Container and connect it to the
Reduxstore.
class InProgressDownloads {
...
};
...
export default connect(mapStateToProps, mapDispatchToProps)(InProgressDownloads);- Define navigation
export const DownloadsTabs = createMaterialTopTabNavigator({
InProgressDownloads: { screen: InProgressDownloads },
...
});
...
const Drawer = createDrawerNavigator({
Downloads: { screen: DownloadsTabs },
},
...
);
...
const AppNavigator = createStackNavigator({
MainDrawer: { screen: Drawer, navigationOptions: { header: null } },
...
});- 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;
}
...- Define the API calls as a Service
import APIBuilder from '../helpers/APIBuilder';
...
const addDownload = downloadLink => APIBuilder.API.post('/api/download', JSON.stringify(downloadLink));
...- Import service and call it in your code
import DownloadService from '../../services/downloadService';
...
await DownloadService.addDownload({ link });- Write tests to validate the new code.
Let's see how we can use the Bassa-mobile project structure (check it out there), to develop the workflow for fetching completed downloads.
- Implement the draft Completed Downloads Container and connect it to the
Reduxstore.
class CompletedDownloads {
...
};
...
export default connect(mapStateToProps)(CompletedDownloads);- Define navigation
export const DownloadsTabs = createMaterialTopTabNavigator({
...
CompletedDownloads: { screen: CompletedDownloads },
});
...
const Drawer = createDrawerNavigator({
Downloads: { screen: DownloadsTabs },
},
...
);
...
const AppNavigator = createStackNavigator({
MainDrawer: { screen: Drawer, navigationOptions: { header: null } },
...
});- 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;- Define the API call as a Service
import APIBuilder from '../helpers/APIBuilder';
...
const getAllDownloads = () => APIBuilder.API.get('/api/downloads/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');
}
}
...- 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>
);
};- Write tests to validate the new code.