Создание приложений React Native с применением Microsoft Graph
В этом руководстве рассказывается о создании приложения React Native, которое использует API microsoft Graph для получения сведений о календаре для пользователя.
Совет
Если вы предпочитаете просто скачать завершенный учебник, вы можете скачать или клонировать GitHub репозиторий.
Предварительные требования
Перед началом этого учебного пособия на компьютере разработки должно быть установлено следующее.
- По крайней мере один из следующих ниже.
- Android Studio и Набор разработки Java (необходимые для запуска примера на Android)
- Xcode (необходимый для запуска примера на iOS)
- Node.js
Вы также должны иметь личную учетную запись Майкрософт с почтовым ящиком на Outlook.com или учетную запись Microsoft work или school. Если у вас нет учетной записи Майкрософт, существует несколько вариантов получения бесплатной учетной записи:
- Вы можете зарегистрироваться на новую личную учетную запись Майкрософт.
- Вы можете зарегистрироваться в программе Microsoft 365 разработчика, чтобы получить бесплатную Microsoft 365 подписку.
Примечание
Этот учебник был написан с React Native CLI, который имеет определенные предпосылки в зависимости от операционной системы и целевых платформ. Инструкции по настройке машины разработки см. в React Native "Начало работы". Версии, используемые для этого руководства, приведены ниже. Действия в этом руководстве могут работать с другими версиями, но они не были проверены.
- Android Studio версии 4.1 с SDK Android 9.0
- Java Development Kit версии 12.0.2
- Xcode версии 12.5.1
- Node.js версии 14.15.0
Отзывы
Обратите внимание на этот учебник в репозитории GitHub.
Создание приложения React Native
Начните с создания нового React Native проекта.
Откройте интерфейс командной строки (CLI) в каталоге, где необходимо создать проект. Запустите следующую команду для запуска средства react-native-cli и создания нового React Native проекта.
npx react-native init GraphTutorial --template react-native-template-typescript
Необязательный: Убедитесь, что среда разработки настроена правильно, запуская проект. В CLI измените каталог на только что созданный каталог GraphTutorial и запустите одну из следующих команд.
- Для iOS:
npx react-native run-ios
- Для Android: запустите экземпляр эмулятора Android и запустите
npx react-native run-android
- Для iOS:
Установка зависимостей
Прежде чем двигаться дальше, установите дополнительные зависимости, которые вы будете использовать позже.
- реактивной навигации для обработки навигации между представлениями в приложении.
- React-native-gesture-handler, react-native-safe-area-context, react-native-screens, react-native-reanimate, and masked-view required by react-navigation.
- react-native-elements и react-native-vector-icons для предоставления значков для пользовательского интерфейса.
- react-native-app-auth для обработки проверки подлинности и управления маркерами.
- async-storage для хранения маркеров.
- datetimepicker для добавления в пользовательский интерфейс даты и времени.
- момент для обработки размывов и сравнения дат и времени.
- windows-iana для перевода Windows часовых поясов в формат IANA.
- microsoft-graph-client для звонков в microsoft Graph.
Откройте CLI в корневом каталоге React Native проекта.
Выполните следующую команду.
npm install @react-navigation/native@5.9.6 @react-navigation/drawer@5.12.7 @react-navigation/stack@5.14.7 npm install @react-native-community/masked-view@0.1.11 react-native-safe-area-context@3.3.0 windows-iana npm install react-native-reanimated@2.2.0 react-native-screens@3.5.0 @react-native-async-storage/async-storage@1.15.5 npm install react-native-elements@3.4.2 react-native-vector-icons@8.1.0 react-native-gesture-handler@1.10.3 npm install react-native-app-auth@6.4.0 moment@2.29.1 moment-timezone @microsoft/microsoft-graph-client@3.0.0 npm install @react-native-community/datetimepicker@3.5.2 npm install @microsoft/microsoft-graph-types --save-dev
Ссылка и настройка зависимостей для iOS
Примечание
Если вы не нацелены на iOS, вы можете пропустить этот раздел.
Откройте CLI в каталоге GraphTutorial/ios .
Выполните следующую команду.
pod install
Откройте файл GraphTutorial/ios/GraphTutorial/Info.plist в текстовом редакторе. Добавьте следующую строку перед последней строкой
</dict>
в файле.<key>UIAppFonts</key> <array> <string>AntDesign.ttf</string> <string>Entypo.ttf</string> <string>EvilIcons.ttf</string> <string>Feather.ttf</string> <string>FontAwesome.ttf</string> <string>FontAwesome5_Brands.ttf</string> <string>FontAwesome5_Regular.ttf</string> <string>FontAwesome5_Solid.ttf</string> <string>Foundation.ttf</string> <string>Ionicons.ttf</string> <string>MaterialIcons.ttf</string> <string>MaterialCommunityIcons.ttf</string> <string>SimpleLineIcons.ttf</string> <string>Octicons.ttf</string> <string>Zocial.ttf</string> </array>
Откройте файл GraphTutorial/ios/GraphTutorial/AppDelegate.h в текстовом редакторе. Замените его содержимое следующим.
#import <React/RCTBridgeDelegate.h> #import <UIKit/UIKit.h> #import "RNAppAuthAuthorizationFlowManager.h" @interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, RNAppAuthAuthorizationFlowManager> @property (nonatomic, strong) UIWindow *window; @property (nonatomic, weak) id<RNAppAuthAuthorizationFlowManagerDelegate> authorizationFlowManagerDelegate; @end
Настройка зависимостей для Android
Примечание
Если вы не ориентированы на Android, вы можете пропустить этот раздел.
Откройте файл GraphTutorial/android/app/build.gradle в редакторе.
Найдите запись
defaultConfig
и добавьте в нее следующее свойствоdefaultConfig
.manifestPlaceholders = [ appAuthRedirectScheme: 'graph-tutorial' ]
Запись
defaultConfig
должна выглядеть так же, как и следующая.defaultConfig { applicationId "com.graphtutorial" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" manifestPlaceholders = [ appAuthRedirectScheme: 'graph-tutorial' ] }
Добавьте следующую строку в конец файла.
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
Сохраните файл.
Проектирование приложения
Приложение будет использовать ящик навигации для перемещения между различными представлениями. На этом шаге вы создайте основные представления, используемые приложением, и реализуйте ящик навигации.
Создание представлений
В этом разделе будут создаваться представления для приложения для поддержки потока проверки подлинности.
Откройте GraphTutorial/index.js и добавьте следующее в верхней части файла перед любыми другими утверждениями
import
.import 'react-native-gesture-handler';
Создайте новый файл в каталоге GraphTutorial с именем AuthContext.tsx и добавьте следующий код.
import * as React from 'react'; type AuthContextType = { signIn: () => Promise<void>; signOut: () => void; } export const AuthContext = React.createContext<AuthContextType>({ signIn: async () => {}, signOut: () => {} });
Создайте новый файл в каталоге GraphTutorial с именем UserContext.tsx и добавьте следующий код.
import * as React from 'react'; import { ImageSourcePropType } from 'react-native'; type UserContextType = { userLoading: boolean; userFirstName: string; userFullName: string; userEmail: string; userTimeZone: string; userPhoto: ImageSourcePropType; } export const UserContext = React.createContext<UserContextType>({ userLoading: true, userFirstName: '', userFullName: '', userEmail: '', userTimeZone: '', userPhoto: require('./images/no-profile-pic.png') });
Создание нового каталога в каталоге GraphTutorial с именем экранов.
Создайте новый файл в каталоге GraphTutorial/screens с именем HomeScreen.tsx. Добавьте указанный ниже код в файл.
import React from 'react'; import { ActivityIndicator, Platform, StyleSheet, Text, View, } from 'react-native'; import { createStackNavigator } from '@react-navigation/stack'; import { UserContext } from '../UserContext'; const Stack = createStackNavigator(); const HomeComponent = () => { const userContext = React.useContext(UserContext); return ( <View style={styles.container}> <ActivityIndicator color={Platform.OS === 'android' ? '#276b80' : undefined} animating={userContext.userLoading} size='large' /> {userContext.userLoading ? null: <Text>Hello {userContext.userFirstName}!</Text>} </View> ); } export default class HomeScreen extends React.Component { render() { return ( <Stack.Navigator> <Stack.Screen name='Home' component={HomeComponent} options={{ headerShown: false }} /> </Stack.Navigator> ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center' } });
Создайте новый файл в каталоге GraphTutorial/screens с именем CalendarScreen.tsx. Добавьте указанный ниже код в файл.
import React from 'react'; import {StyleSheet, Text, View} from 'react-native'; import {createStackNavigator} from '@react-navigation/stack'; const Stack = createStackNavigator(); // Temporary placeholder view const CalendarComponent = () => ( <View style={styles.container}> <Text>Calendar</Text> </View> ); export default class CalendarScreen extends React.Component { render() { return ( <Stack.Navigator> <Stack.Screen name='Calendar' component={CalendarComponent} options={{ headerShown: false, }} /> </Stack.Navigator> ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });
Создайте новый файл в каталоге GraphTutorial/screens с именем SignInScreen.tsx. Добавьте указанный ниже код в файл.
// Adapted from https://reactnavigation.org/docs/auth-flow import React from 'react'; import {Alert, Button, StyleSheet, View} from 'react-native'; import {ParamListBase} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; import {AuthContext} from '../AuthContext'; type SignInProps = { navigation: StackNavigationProp<ParamListBase>; }; export default class SignInScreen extends React.Component<SignInProps> { static contextType = AuthContext; _signInAsync = async () => { await this.context.signIn(); }; componentDidMount() { this.props.navigation.setOptions({ title: 'Please sign in', headerShown: true, }); } render() { return ( <View style={styles.container}> <Button title='Sign In' onPress={this._signInAsync} /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });
Создайте новый файл в каталоге GraphTutorial/screens с именем AuthLoadingScreen.tsx. Добавьте указанный ниже код в файл.
// Adapted from https://reactnavigation.org/docs/auth-flow import React from 'react'; import { ActivityIndicator, Platform, Text, StyleSheet, View, } from 'react-native'; export default class AuthLoadingScreen extends React.Component { render() { return ( <View style={styles.container}> <ActivityIndicator color={Platform.OS === 'android' ? '#276b80' : undefined} size='large' /> <Text style={styles.statusText}>Logging in...</Text> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center' }, statusText: { marginTop: 10 } });
Создание ящика навигации
В этом разделе вы создадим меню для приложения и обновим приложение для перемещения между экранами с помощью реактивной навигации.
Создание нового каталога в каталоге GraphTutorial с именем меню.
Создайте новый файл в каталоге GraphTutorial/menus с именем DrawerMenu.tsx. Добавьте указанный ниже код в файл.
import React, {FC} from 'react'; import { Alert, Image, StyleSheet, Text, View, ImageSourcePropType, } from 'react-native'; import { createDrawerNavigator, DrawerContentScrollView, DrawerItem, DrawerItemList, DrawerContentComponentProps, } from '@react-navigation/drawer'; import {ParamListBase} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; import {AuthContext} from '../AuthContext'; import {UserContext} from '../UserContext'; import HomeScreen from '../screens/HomeScreen'; import CalendarScreen from '../screens/CalendarScreen'; const Drawer = createDrawerNavigator(); type CustomDrawerContentProps = DrawerContentComponentProps & { userName: string; userEmail: string; userPhoto: ImageSourcePropType; signOut: () => void; }; type DrawerMenuProps = { navigation: StackNavigationProp<ParamListBase>; }; const CustomDrawerContent: FC<CustomDrawerContentProps> = props => ( <DrawerContentScrollView {...props}> <View style={styles.profileView}> <Image source={props.userPhoto} resizeMode='contain' style={styles.profilePhoto} /> <Text style={styles.profileUserName}>{props.userName}</Text> <Text style={styles.profileEmail}>{props.userEmail}</Text> </View> <DrawerItemList {...props} /> <DrawerItem label='Sign Out' onPress={props.signOut} /> </DrawerContentScrollView> ); export default class DrawerMenuContent extends React.Component<DrawerMenuProps> { static contextType = AuthContext; state = { // TEMPORARY userLoading: true, userFirstName: 'Adele', userFullName: 'Adele Vance', userEmail: 'adelev@contoso.com', userTimeZone: 'UTC', userPhoto: require('../images/no-profile-pic.png'), }; _signOut = async () => { this.context.signOut(); }; async componentDidMount() { this.props.navigation.setOptions({ headerShown: false, }); } render() { const userLoaded = !this.state.userLoading; return ( <UserContext.Provider value={this.state}> <Drawer.Navigator drawerType='front' screenOptions={{ headerShown: true, headerStyle: { backgroundColor: '#276b80', }, headerTintColor: 'white', }} drawerContent={props => ( <CustomDrawerContent {...props} userName={this.state.userFullName} userEmail={this.state.userEmail} userPhoto={this.state.userPhoto} signOut={this._signOut} /> )}> <Drawer.Screen name='Home' component={HomeScreen} options={{drawerLabel: 'Home', headerTitle: 'Welcome'}} /> {userLoaded && ( <Drawer.Screen name='Calendar' component={CalendarScreen} options={{drawerLabel: 'Calendar'}} /> )} </Drawer.Navigator> </UserContext.Provider> ); } } const styles = StyleSheet.create({ container: { flex: 1, }, profileView: { alignItems: 'center', padding: 10, }, profilePhoto: { width: 80, height: 80, borderRadius: 40, }, profileUserName: { fontWeight: '700', }, profileEmail: { fontWeight: '200', fontSize: 10, }, });
Создание нового каталога в каталоге GraphTutorial с именем изображений.
Добавьте изображение профиля по умолчанию с именемno-profile-pic.png в этом каталоге. Вы можете использовать любое изображение, которое вам нравится, или использовать его из этого примера.
Откройте файл GraphTutorial/App.tsx и замените все содержимое следующим.
// Adapted from https://reactnavigation.org/docs/auth-flow import * as React from 'react'; import {NavigationContainer, ParamListBase} from '@react-navigation/native'; import { createStackNavigator, StackNavigationProp, } from '@react-navigation/stack'; import {AuthContext} from './AuthContext'; import SignInScreen from './screens/SignInScreen'; import DrawerMenuContent from './menus/DrawerMenu'; import AuthLoadingScreen from './screens/AuthLoadingScreen'; const Stack = createStackNavigator(); type Props = { navigation: StackNavigationProp<ParamListBase>; }; export default function App({navigation}: Props) { const [state, dispatch] = React.useReducer( (prevState: any, action: any) => { switch (action.type) { case 'RESTORE_TOKEN': return { ...prevState, userToken: action.token, isLoading: false, }; case 'SIGN_IN': return { ...prevState, isSignOut: false, userToken: action.token, }; case 'SIGN_OUT': return { ...prevState, isSignOut: true, userToken: null, }; } }, { isLoading: true, isSignOut: false, userToken: null, }, ); React.useEffect(() => { const bootstrapAsync = async () => { let userToken = null; // TEMPORARY dispatch({type: 'RESTORE_TOKEN', token: userToken}); }; bootstrapAsync(); }, []); const authContext = React.useMemo( () => ({ signIn: async () => { dispatch({type: 'SIGN_IN', token: 'placeholder-token'}); }, signOut: async () => { dispatch({type: 'SIGN_OUT'}); }, }), [], ); return ( <AuthContext.Provider value={authContext}> <NavigationContainer> <Stack.Navigator> {state.isLoading ? ( <Stack.Screen name='Loading' component={AuthLoadingScreen} /> ) : state.userToken == null ? ( <Stack.Screen name='SignIn' component={SignInScreen} /> ) : ( <Stack.Screen name='Main' component={DrawerMenuContent} /> )} </Stack.Navigator> </NavigationContainer> </AuthContext.Provider> ); }
Сохраните все изменения.
Перезагрузите приложение в эмуляторе.
Меню приложения должно работать для перемещения между двумя фрагментами и изменения при нажатии кнопки Вход или Вход .
Регистрация приложения на портале
В этом упражнении будет создаваться новое родной приложение Azure AD с Azure Active Directory центра администрирования.
Откройте браузер и перейдите в Центр администрирования Azure Active Directory и войдите с помощью личной учетной записи (т.е. учетной записи Майкрософт) или рабочей или учебной учетной записи.
Выберите Azure Active Directory на панели навигации слева, затем выберите Регистрация приложений в разделе Управление.
Выберите Новая регистрация. На странице Зарегистрировать приложение задайте необходимые значения следующим образом.
- Введите имя
React Native Graph Tutorial
. - Введите поддерживаемые типы учетных записей для учетных записей в любом каталоге организаций и личных учетных записей Microsoft.
- В статье Перенаправление URI измените отсев на общедоступный клиент (мобильный & рабочий стол) и установите значение
graph-tutorial://react-native-auth/
.
- Введите имя
Нажмите Зарегистрировать. На странице React Native Graph учебника скопируйте значение ID приложения (клиента) и сохраните его, оно потребуется на следующем шаге.
Добавление проверки подлинности с помощью Azure AD
В этом упражнении вы расширит приложение от предыдущего упражнения для поддержки проверки подлинности с помощью Azure AD. Это необходимо для получения необходимого маркера доступа OAuth для вызова microsoft Graph. Для этого в приложение будет интегрирована библиотека react-native-app-auth .
Создание нового каталога в каталоге GraphTutorial с именем auth.
Создайте новый файл в каталоге GraphTutorial/auth с именем AuthConfig.ts. Добавьте указанный ниже код в файл.
export const AuthConfig = { appId: 'YOUR_APP_ID_HERE', appScopes: [ 'openid', 'offline_access', 'profile', 'User.Read', 'MailboxSettings.Read', 'Calendars.ReadWrite' ] };
Замените
YOUR_APP_ID_HERE
с помощью ID приложения из регистрации приложения.
Важно!
Если вы используете источник управления, например git, то сейчас самое время исключить файл AuthConfig.ts из-под контроля источника, чтобы избежать случайной утечки вашего ID приложения.
Реализация входа в систему
В этом разделе вы создадим класс помощника проверки подлинности и обновим приложение, чтобы войти и выйти.
Создайте новый файл в каталоге GraphTutorial/auth с именем AuthManager.ts. Добавьте указанный ниже код в файл.
import AsyncStorage from '@react-native-async-storage/async-storage'; import { authorize, refresh, AuthConfiguration } from 'react-native-app-auth'; import { Platform } from 'react-native'; import moment from 'moment'; import { AuthConfig } from './AuthConfig'; const config: AuthConfiguration = { clientId: AuthConfig.appId, redirectUrl: 'graph-tutorial://react-native-auth/', scopes: AuthConfig.appScopes, additionalParameters: { prompt: 'select_account' }, serviceConfiguration: { authorizationEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', tokenEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', } }; export class AuthManager { static signInAsync = async () => { const result = await authorize(config); console.log(result.accessToken); // Store the access token, refresh token, and expiration time in storage await AsyncStorage.setItem('userToken', result.accessToken); await AsyncStorage.setItem('refreshToken', result.refreshToken); await AsyncStorage.setItem('expireTime', result.accessTokenExpirationDate); } static signOutAsync = async () => { // Clear storage await AsyncStorage.removeItem('userToken'); await AsyncStorage.removeItem('refreshToken'); await AsyncStorage.removeItem('expireTime'); } static getAccessTokenAsync = async() => { const expireTime = await AsyncStorage.getItem('expireTime'); if (expireTime !== null) { // Get expiration time - 5 minutes // If it's <= 5 minutes before expiration, then refresh const expire = moment(expireTime).subtract(5, 'minutes'); const now = moment(); if (now.isSameOrAfter(expire)) { // Expired, refresh console.log('Refreshing token'); const refreshToken = await AsyncStorage.getItem('refreshToken'); console.log(`Refresh token: ${refreshToken}`); const result = await refresh(config, { refreshToken: refreshToken || '' }); // Store the new access token, refresh token, and expiration time in storage await AsyncStorage.setItem('userToken', result.accessToken); await AsyncStorage.setItem('refreshToken', result.refreshToken || ''); await AsyncStorage.setItem('expireTime', result.accessTokenExpirationDate); return result.accessToken; } // Not expired, just return saved access token const accessToken = await AsyncStorage.getItem('userToken'); return accessToken; } return null; } }
Откройте файл GraphTutorial/App.tsx
import
и добавьте следующее утверждение в верхнюю часть файла.import {AuthManager} from './auth/AuthManager';
Замените существующее
authContext
объявление следующим.const authContext = React.useMemo( () => ({ signIn: async () => { await AuthManager.signInAsync(); const token = await AuthManager.getAccessTokenAsync(); dispatch({ type: 'SIGN_IN', token: token }); }, signOut: async () => { await AuthManager.signOutAsync(); dispatch({ type: 'SIGN_OUT' }); } }), [] );
Откройте файл GraphTutorial/menus/DrawerMenu.tsx
import
и добавьте следующее утверждение в верхнюю часть файла.import {AuthManager} from '../auth/AuthManager';
Добавьте в функцию следующий
componentDidMount
код.try { const accessToken = await AuthManager.getAccessTokenAsync(); // TEMPORARY this.setState({userFirstName: accessToken, userLoading: false}); } catch (error) { Alert.alert( 'Error getting token', JSON.stringify(error), [ { text: 'OK' } ], { cancelable: false } ); }
Сохраните изменения и перезагрузите приложение в эмуляторе.
При входе в приложение необходимо увидеть маркер доступа, отображаемый на экране Welcome .
Получение сведений о пользователе
В этом разделе вы создадим настраиваемый поставщик проверки подлинности для клиентской библиотеки Graph, создадим класс помощников для удержания всех вызовов в Microsoft Graph DrawerMenuContent
и обновим класс для использования этого нового класса для входа в систему пользователя.
Создание нового каталога в каталоге GraphTutorial с именем graph.
Создайте новый файл в каталоге GraphTutorial/graph с именем GraphAuthProvider.ts. Добавьте указанный ниже код в файл.
import { AuthManager } from '../auth/AuthManager'; // Used by Graph client to get access tokens // See https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/docs/CustomAuthenticationProvider.md export class GraphAuthProvider { getAccessToken = async() => { const token = await AuthManager.getAccessTokenAsync(); return token || ''; } }
Создайте новый файл в каталоге GraphTutorial/graph с именем GraphManager.ts. Добавьте указанный ниже код в файл.
import {Client} from '@microsoft/microsoft-graph-client'; import {GraphAuthProvider} from './GraphAuthProvider'; // Set the authProvider to an instance // of GraphAuthProvider const clientOptions = { authProvider: new GraphAuthProvider(), }; // Initialize the client const graphClient = Client.initWithMiddleware(clientOptions); export class GraphManager { static getUserAsync = async () => { // GET /me return await graphClient .api('/me') .select('displayName,givenName,mail,mailboxSettings,userPrincipalName') .get(); }; }
Откройте файл GraphTutorial/views/DrawerMenu.tsx
import
и добавьте в верхней части файла следующие утверждения.import {GraphManager} from '../graph/GraphManager'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
Замените
componentDidMount
метод следующим.async componentDidMount() { this.props.navigation.setOptions({ headerShown: false, }); try { // Get the signed-in user from Graph const user: MicrosoftGraph.User = await GraphManager.getUserAsync(); // Update UI with display name and email this.setState({ userLoading: false, userFirstName: user.givenName!, userFullName: user.displayName!, // Work/School accounts have email address in mail attribute // Personal accounts have it in userPrincipalName userEmail: user.mail! || user.userPrincipalName!, userTimeZone: user.mailboxSettings?.timeZone! }); } catch(error) { Alert.alert( 'Error getting user', JSON.stringify(error), [ { text: 'OK' } ], { cancelable: false } ); } }
Если вы сохраните изменения и перезагрузите приложение сейчас, после внесения в него пользовательский интерфейс обновляется с отображаемого имени пользователя и адресом электронной почты.
Просмотр календаря
В этом упражнении вы будете включать Graph Microsoft в приложение. Для этого приложения вы будете использовать клиентскую библиотеку Microsoft Graph JavaScript для звонков в Microsoft Graph.
Получение событий календаря из Outlook
В этом разделе вы расширит GraphManager
CalendarScreen
класс, чтобы добавить функцию для получения событий пользователя на текущей неделе и обновить для использования этих новых функций.
Откройте файл GraphTutorial/graph/GraphManager.tsx и добавьте следующий метод в
GraphManager
класс.static getCalendarView = async(start: string, end: string, timezone: string) => { // GET /me/calendarview return await graphClient.api('/me/calendarview') .header('Prefer', `outlook.timezone="${timezone}"`) .query({ startDateTime: start, endDateTime: end}) // $select='subject,organizer,start,end' // Only return these fields in results .select('subject,organizer,start,end') // $orderby=createdDateTime DESC // Sort results by when they were created, newest first .orderby('start/dateTime') .top(50) .get(); }
Примечание
Рассмотрим, что делает
getCalendarView
код.- Вызывается URL-адрес
/v1.0/me/calendarView
. - Функция
header
добавляет загонPrefer: outlook.timezone
в запрос, в результате чего время отклика будет в предпочтительном часовом поясе пользователя. - Функция
query
добавляет параметрыstartDateTime
и параметрыendDateTime
, определяя окно времени для представления календаря. - Функция
select
ограничивает поля, возвращаемые для каждого события, только теми, которые на самом деле будет использовать приложение. - Функция
orderby
сортировать результаты к началу. - Функция
top
ограничивает результаты первыми 50 событиями.
- Вызывается URL-адрес
Откройте GraphTutorial/views/CalendarScreen.tsx и замените все содержимое следующим кодом.
import React from 'react'; import { ActivityIndicator, Alert, FlatList, Modal, Platform, ScrollView, StyleSheet, Text, View, } from 'react-native'; import {createStackNavigator} from '@react-navigation/stack'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; import moment from 'moment-timezone'; import {findIana} from 'windows-iana'; import {UserContext} from '../UserContext'; import {GraphManager} from '../graph/GraphManager'; const Stack = createStackNavigator(); const CalendarState = React.createContext<CalendarScreenState>({ loadingEvents: true, events: [], }); type CalendarScreenState = { loadingEvents: boolean; events: MicrosoftGraph.Event[]; }; // Temporary JSON view const CalendarComponent = () => { const calendarState = React.useContext(CalendarState); return ( <View style={styles.container}> <Modal visible={calendarState.loadingEvents}> <View style={styles.loading}> <ActivityIndicator color={Platform.OS === 'android' ? '#276b80' : undefined} animating={calendarState.loadingEvents} size='large' /> </View> </Modal> <ScrollView> <Text>{JSON.stringify(calendarState.events, null, 2)}</Text> </ScrollView> </View> ); }; export default class CalendarScreen extends React.Component { static contextType = UserContext; state: CalendarScreenState = { loadingEvents: true, events: [], }; async componentDidMount() { try { const tz = this.context.userTimeZone || 'UTC'; // Convert user's Windows time zone ("Pacific Standard Time") // to IANA format ("America/Los_Angeles") // Moment.js needs IANA format const ianaTimeZone = findIana(tz)[0]; // Get midnight on the start of the current week in the user's // time zone, but in UTC. For example, for PST, the time value // would be 07:00:00Z const startOfWeek = moment .tz(ianaTimeZone!.valueOf()) .startOf('week') .utc(); const endOfWeek = moment(startOfWeek).add(7, 'day'); const events = await GraphManager.getCalendarView( startOfWeek.format(), endOfWeek.format(), tz, ); this.setState({ loadingEvents: false, events: events.value, }); } catch (error) { Alert.alert( 'Error getting events', JSON.stringify(error), [ { text: 'OK', }, ], {cancelable: false}, ); } } render() { return ( <CalendarState.Provider value={this.state}> <Stack.Navigator> <Stack.Screen name='Calendar' component={CalendarComponent} options={{ headerShown: false, }} /> </Stack.Navigator> </CalendarState.Provider> ); } } const styles = StyleSheet.create({ container: { flex: 1, }, loading: { flex: 1, justifyContent: 'center', alignItems: 'center', }, eventItem: { padding: 10, }, eventSubject: { fontWeight: '700', fontSize: 18, }, eventOrganizer: { fontWeight: '200', }, eventDuration: { fontWeight: '200', }, });
Теперь вы можете запустить приложение, войти и нажмите элемент навигации Календарь в меню. Вы должны увидеть сброс JSON событий в приложении.
Отображение результатов
Теперь вы можете заменить свалку JSON чем-то, чтобы отобразить результаты в удобной для пользователя манере. В этом разделе вы добавим на FlatList
экран календаря для отображения событий.
Добавьте следующий метод выше объявления
CalendarScreen
класса.const convertDateTime = (dateTime: string): string => { return moment(dateTime).format('MMM Do H:mm a'); };
Замените
ScrollView
методCalendarComponent
следующим.<FlatList data={calendarState.events} renderItem={({item}) => ( <View style={styles.eventItem}> <Text style={styles.eventSubject}>{item.subject}</Text> <Text style={styles.eventOrganizer}> {item.organizer!.emailAddress!.name} </Text> <Text style={styles.eventDuration}> {convertDateTime(item.start!.dateTime!)} -{' '} {convertDateTime(item.end!.dateTime!)} </Text> </View> )} />
Запустите приложение, вопишите и нажмите элемент навигации Календарь . Список событий.
Создание нового события
В этом разделе вы добавим возможность создания событий в календаре пользователя.
Создание нового экрана событий
Откройте ./graph/GraphManager.ts и добавьте в класс следующую
GraphManager
функцию.static createEvent = async(newEvent: any) => { // POST /me/events await graphClient.api('/me/events') .post(newEvent); }
Эта функция использует Graph SDK для создания нового события.
Создайте новый файл на экранах с именем NewEventScreen.tsx и добавьте следующий код.
import React from 'react'; import { ActivityIndicator, Alert, Button, Modal, Platform, ScrollView, StyleSheet, Text, View, } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; import { createStackNavigator } from '@react-navigation/stack'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; import moment from 'moment-timezone'; import { UserContext } from '../UserContext'; import { GraphManager } from '../graph/GraphManager'; import { TextInput } from 'react-native-gesture-handler'; const Stack = createStackNavigator(); const NewEventState = React.createContext<NewEventState>({ isCreating: false, subject: '', attendees: '', body: '', timeZone: '', startDate: new Date(), endDate: new Date(), disableCreate: () => { return true }, updateValue: () => {} }); type NewEventState = { isCreating: boolean; subject: string; attendees: string; body: string; timeZone: string; startDate: Date; endDate: Date; disableCreate: () => boolean; updateValue: (newValue: string | Date | boolean, fieldName: string) => void; } type DateTimeInputProps = { value: Date; onChange: (event: Event, newValue: Date | undefined) => void; } // The picker acts very differently on Android and iOS // iOS can use a single picker for both date and time, // where Android requires two. Also the iOS version can // be displayed all the time, while the Android version is a // modal pop-up. Encapsulating this into a reusable component const DateTimeInput = (props: DateTimeInputProps) => { const [showDatePicker, setShowDatePicker] = React.useState(false); const [showTimePicker, setShowTimePicker] = React.useState(Platform.OS === 'ios'); return ( <View style={Platform.OS === 'android' ? styles.dateTime : null}> { Platform.OS === 'android' && <Text style={styles.time} onPress={()=>{setShowTimePicker(true)}}> {formatTime(props.value)} </Text> } { showTimePicker && <DateTimePicker mode={Platform.OS === 'ios' ? 'datetime' : 'time'} value={props.value} onChange={(e, d) => { setShowTimePicker(Platform.OS === 'ios'); if (d) props.onChange(e,d); }} /> } { Platform.OS === 'android' && <Text style={styles.date} onPress={()=>{setShowDatePicker(true)}}> {formatDate(props.value)} </Text> } { showDatePicker && Platform.OS === 'android' && <DateTimePicker mode='date' value={props.value} onChange={(e, d) => { setShowDatePicker(Platform.OS === 'ios'); if (d) props.onChange(e,d); }} /> } </View> ) } const NewEventComponent = () => { const newEventState = React.useContext(NewEventState); const createEvent = async () => { newEventState.updateValue(true, 'isCreating'); // Create a new Event object with the // required fields const newEvent: MicrosoftGraph.Event = { subject: newEventState.subject, start: { dateTime: moment(newEventState.startDate).format('YYYY-MM-DDTHH:mm:ss'), timeZone: newEventState.timeZone }, end: { dateTime: moment(newEventState.endDate).format('YYYY-MM-DDTHH:mm:ss'), timeZone: newEventState.timeZone } }; // Only add attendees if the user specified them if (newEventState.attendees.length > 0) { newEvent.attendees = []; // Value should be a ;-delimited list of email addresses // NOTE: The app does no validation of this const emails = newEventState.attendees.split(';') emails.forEach((email) => { newEvent.attendees!.push({ emailAddress: { address: email } }); }); } // Only add body if the user specified one if (newEventState.body.length > 0) { newEvent.body = { content: newEventState.body, // For simplicity, add it as a plain-text body contentType: 'text' }; } await GraphManager.createEvent(newEvent); Alert.alert('Success', 'Event created', [ { text: 'OK', onPress: () => { newEventState.updateValue(false, 'isCreating'); } } ] ); } return ( <ScrollView style={styles.container}> <Modal visible={newEventState.isCreating}> <View style={styles.loading}> <ActivityIndicator color={Platform.OS === 'android' ? '#276b80' : undefined} animating={newEventState.isCreating} size='large' /> </View> </Modal> <View style={styles.formField}> <Text style={styles.fieldLabel}>Subject</Text> <TextInput style={styles.textInput} value={newEventState.subject} onChangeText={(text) => newEventState.updateValue(text, 'subject')} /> </View> <View style={styles.formField}> <Text style={styles.fieldLabel}>Attendees</Text> <TextInput style={styles.textInput} placeholder="Email (separate multiple with ';')" value={newEventState.attendees} onChangeText={(text) => newEventState.updateValue(text, 'attendees')} /> </View> <View style={styles.formField}> <Text style={styles.fieldLabel}>Start</Text> <DateTimeInput value={newEventState.startDate} onChange={(e, date) => newEventState.updateValue(date!, 'startDate')} /> </View> <View style={styles.formField}> <Text style={styles.fieldLabel}>End</Text> <DateTimeInput value={newEventState.endDate} onChange={(e, date) => newEventState.updateValue(date!, 'endDate')} /> </View> <View style={styles.formField}> <TextInput style={styles.multiLineTextInput} multiline={true} textAlignVertical='top' placeholder='Body' value={newEventState.body} onChangeText={(text) => newEventState.updateValue(text, 'body')} /> </View> <View style={styles.formField}> <Button title="Create" disabled={newEventState.disableCreate()} onPress={createEvent}/> </View> </ScrollView> ); } const formatTime = (dateTime: Date): string => { return moment(dateTime).format('h:mm A'); } const formatDate = (dateTime: Date): string => { return moment(dateTime).format('MMM D, YYYY'); } // When first loading the form, set the start time // to the nearest hour or half-hour const getDefaultStart = (): Date => { const now = moment().startOf('minute'); const offset = 30 - (now.minute() % 30); return now.add(offset, 'minutes').toDate(); } // When first loading the form, set the end time // to start + 30 min const getDefaultEnd = (): Date => { return moment(getDefaultStart()).add(30, 'minutes').toDate(); } export default class NewEventScreen extends React.Component { static contextType = UserContext; // Disable the create button if: // - App is waiting for the result of create request // - Subject is empty // - Start time is after end time disableCreate = () => { return this.state.isCreating || this.state.subject.length <= 0 || moment(this.state.startDate).isAfter(this.state.endDate); } onStateValueChange = (newValue: string | Date | boolean, fieldName: string) => { this.setState({ [fieldName]: newValue }); } state: NewEventState = { isCreating: false, subject: '', attendees: '', body: '', timeZone: this.context.userTimeZone, startDate: getDefaultStart(), endDate: getDefaultEnd(), disableCreate: this.disableCreate, updateValue: this.onStateValueChange }; render() { return ( <NewEventState.Provider value={this.state}> <Stack.Navigator> <Stack.Screen name='NewEvent' component={ NewEventComponent } options={{ headerShown: false }} /> </Stack.Navigator> </NewEventState.Provider> ); } } const styles = StyleSheet.create({ container: { flex: 1 }, loading: { flex: 1, justifyContent: 'center', alignItems: 'center' }, formField: { paddingHorizontal: 10, paddingVertical: 5 }, fieldLabel: { fontWeight: '700', marginBottom: 10 }, textInput: { borderColor: 'gray', borderWidth: 1, height: 40, padding: 10 }, multiLineTextInput: { borderColor: 'gray', borderWidth: 1, height: 200, padding: 10 }, time: { padding: 10, backgroundColor: '#e6e6e6', color: '#147efb', marginRight: 10 }, date: { padding: 10, backgroundColor: '#e6e6e6', color: '#147efb' }, dateTime: { flexDirection: 'row' } });
Рассмотрим, что делает
createEvent
функция. Он создает объект сMicrosoftGraph.Event
использованием значений из формы, а затем передает этот объект функцииGraphManager.createEvent
.Откройте ./menus/DrawerMenu.tsx
import
и добавьте следующее утверждение в верхней части файла.import NewEventScreen from '../screens/NewEventScreen';
Добавьте следующий код внутри элемента
<Drawer.Navigator>
, прямо над строкой</Drawer.Navigator>
.{userLoaded && ( <Drawer.Screen name='NewEvent' component={NewEventScreen} options={{drawerLabel: 'New event'}} /> )}
Сохраните изменения и перезапустите или обновите приложение. Выберите параметр New event в меню, чтобы добраться до новой формы события.
Заполните форму и выберите Create.
Поздравляем!
Вы завершили учебный React Native Microsoft Graph. Теперь, когда у вас есть рабочее приложение, которое вызывает Microsoft Graph, вы можете экспериментировать и добавлять новые функции. В обзоре microsoft Graph, чтобы увидеть все данные, к ним можно получить доступ с помощью microsoft Graph.
Отзывы
Возникла проблема с этим разделом? Если это так, отправьте нам отзыв, чтобы мы исправили этот раздел.