1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 【HMS Core】School Diary应用集成多个HMS Core服务 更好的体验华为生态系统

【HMS Core】School Diary应用集成多个HMS Core服务 更好的体验华为生态系统

时间:2019-05-02 08:52:20

相关推荐

【HMS Core】School Diary应用集成多个HMS Core服务 更好的体验华为生态系统

一、介绍

总览

通过建立本次的School Diary应用,您可以更好地体验华为生态系统的组成部分,包括认证服务、云存储和云数据库等Serverless服务。此外您还可以了解到如何使用账号服务集成应用登录功能。老师和学生两种角色的匹配过程是本应用的一大特色。该过程涉及二维码扫描、用户头像保存以及在数据库中匹配细节等环节。

您将建立什么

在本次Codelab中,您将建立一个集成认证服务、统一扫码服务、云数据库和云存储等服务接口的School Diary项目,创建一款端到端的用于为学生和老师处理学校作业任务的应用。老师们可以创建、分发、批改和关闭作业任务。学生们可以查看任务,并上传图片提交作业。

您将学会什么

使用认证服务登录应用。

扫描二维码并匹配老师和学生。

使用云数据库操作让老师们能够创建、分发、批改和关闭作业任务。

使用云存储服务让学生们能够上传和更新作业图片。

二、您需要什么

Duration: 2:00

开发环境

一部安装Window10操作系统的台式电脑或笔记本。

一部装有HMS Core (APK) 5.0.0.300或以上版本的华为手机。

已通过验证的华为账号。

三、能力接入准备

Duration: 10:00

在接入所需的SDK前,您需要完成以下准备:

在AppGallery Connect上创建一个应用。

创建一个安卓项目。

生成签名证书。

生成签名证书指纹。

配置指纹。

添加应用包名并保存配置文件。

在项目级build.gradle文件中添加AppGallery Connect插件和Maven仓。

在Android Studio中配置签名证书。

详情请参见AppGallery Connect上开通API服务。

您需要首先注册成为一名开发者才能进行以上操作。

四、开通服务

Duration: 4:00

在接入相关SDK前,您需要在AppGallery Connect控制台上开启所需权限。操作如下:

1、登录AppGallery Connect 点击“项目设置”中“API管理”页签,开通如下服务的API权限。

认证服务(华为账号认证方式)

云数据库

云存储

统一扫码服务

说明:以上API权限默认已开通。如未开通,请手动开通。

2、在弹出页面设置数据处理位置。

五、集成服务

Duration: 4:00

您需要集成云数据库SDK到您的Android Studio项目中。

1、登录AppGallery Connect并点击“我的项目”。

2、选择项目,在应用下拉列表中选择需要集成SDK的应用。

3、选择“项目设置”,进入“常规”页面。在“应用”区域,点击下载“agconnect-services.json”文件。

4、将“agconnect-services.json”文件复制到项目中。

5、在Android Studio中打开项目级“build.gradle”文件。前往allprojects > repositories,然后在buildscript > repositories中配置Maven仓地址。

6、在buildscript > dependencies中配置AppGallery Connect插件地址。

buildscript { dependencies { classpath 'com.huawei.agconnect:agcp:<version>'} }

7、在应用级build.gradle文件中添加AppGallery Connect插件。

apply plugin: 'com.huawei.agconnect'

8、(可选)在Application类的onCreate方法中添加初始化代码。

if (AGConnectInstance.getInstance() == null) {AGConnectInstance.initialize(getApplicationContext()); }

9、在应用级build.gradle文件中的dependencies代码块中添加所需依赖地址。

implementation 'com.huawei.agconnect:agconnect-auth:<version>'implementation 'com.huawei.hms:hwid:<version>'implementation 'com.huawei.hms:scan:<version>'implementation "com.huawei.agconnect:agconnect-storage:<version>"implementation 'com.huawei.agconnect:agconnect-cloud-database:<version>'

六、设计UI

Duration: 5:00

为您的应用设计如下UI。

老师和学生的登录界面UI。

老师和学生的匹配界面UI。

老师和学生的作业管理界面UI。

学生登录界面UI

老师登录界面UI

七、前提准备

Duration: 5:00

认证服务

本次将使用华为账号登录方式。因此,您需要在AppGallery Connect上开启认证服务的华为账号认证方式。否则,登录将失败。

登录AppGallery Connect并点击“我的项目”。

找到并点击项目。

点击“构建”>“认证服务”。如果您首次使用认证服务,请点击“立即开通”。

选择“认证方式”页签,在“操作”列中选择“华为账号”。

云数据库

操作云数据,需要您先开通该服务,然后创建存储区和有着所需字段的云数据库对象。

1、登录AppGallery Connect并点击“我的项目”。

2、点击您的项目。

3、选择“构建”>“云数据库”。如果您首次使用云数据库,请点击“立即开通”。

4、在弹出的页面设置数据处理位置。

5、点击“新增”打开“新增对象类型”页面。

6、设置“对象类型名”为“TaskItem”,点击“下一步”。

7、单击“新增字段”添加如下字段,点击“下一步”。

8、单击“下一步”,添加索引。

9、设置角色和对应权限。

10、单击“确定”。返回对象类型列表,查看已创建的对象类型。

11、按照上述操作添加Loginmapping和UserData对象类型。

Loginmapping

UserData

12、单击“导出”。

13、选择导出文件格式。此处选择“java格式”,选择java文件类型为“android”,输入包名称,单击“确定”。对象类型文件会以zip形式导出至本地。

14、提取压缩包中的文件至项目的model包里。

15、选择“存储区”页签。

16、单击“新增”,进入创建存储区页面。

云存储

使用云存储服务,您需要首先启用它,并在开始编程前完成下述步骤。

1、云存储开通后,创建一个存储实例并赋名。单击“下一步”。

2、制定安全策略来设置用户是否需要经过验证才能访问存储。

3、完成上述步骤后您就可以使用云存储服务了。

统一扫码服务

统一扫码服务属于HMS Core服务,您无需在AppGallery Connect上进行配置。

八、实现功能

Duration: 15:00

完成前提准备后,在您的应用中使用认证服务、云数据库、云存储和统一扫码服务。

1、前往登录界面,输入如下代码实现带有华为账号登录按钮的登录功能。

binding.loginButton.setOnClickListener(view -> {showProgressDialog("Login..." );HuaweiIdAuthParamsHelper huaweiIdAuthParamsHelper = new HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM);List<Scope> scopeList = new ArrayList<>();scopeList.add(new Scope(HwIDConstant.SCOPE.ACCOUNT_BASEPROFILE));huaweiIdAuthParamsHelper.setScopeList(scopeList);HuaweiIdAuthParams authParams = huaweiIdAuthParamsHelper.setAccessToken().createParams();HuaweiIdAuthService service = HuaweiIdAuthManager.getService(LoginActivity.this, authParams);startActivityForResult(service.getSignInIntent(), REQUEST_CODE_SIGN_IN);});

2、实现onActivityResult。

@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == REQUEST_CODE_SIGN_IN) {Task<AuthHuaweiId> authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data);if (authHuaweiIdTask.isSuccessful()) {AuthHuaweiId huaweiAccount = authHuaweiIdTask.getResult();AGConnectAuthCredential credential = HwIdAuthProvider.credentialWithToken(huaweiAccount.getAccessToken());AGConnectAuth.getInstance().signIn(credential).addOnSuccessListener(signInResult -> {hideDialog();AGConnectUser user = signInResult.getUser();validateLogin();}).addOnFailureListener(e -> {hideDialog();Log.e(getString(R.string.SIGN_IN_FAILED_TAG), e.getLocalizedMessage());});} else {Log.e(getString(R.string.SIGN_IN_FAILED_TAG), getString(R.string.SIGN_IN_FAILED_MSG));hideDialog();}}}

3、创建wrapper类用于云数据库存储区初始化和操作对象,例如注入新用户和认证存在用户等。

public class CloudDBZoneWrapper {private static final String TAG = "CloudDBZoneWrapper";private static final String CLOUD_DB_NAME = "SchoolDB";private AGConnectCloudDB mCloudDB;private CloudDBZone mCloudDBZone;private CloudDBZoneConfig mConfig;private UiTaskCallBack mUiTaskCallBack = UiTaskCallBack.DEFAULT;private UiStudentCallBack mUiStudentCallBack = UiStudentCallBack.DEFAULT;public CloudDBZoneWrapper() {SchoolDiaryApplication.setRegionRoutePolicy(AGConnectInstance.getInstance().getOptions().getRoutePolicy());mCloudDB = AGConnectCloudDB.getInstance();}/***设置存储位置*/public void setStorageLocation(Context context) {if (mCloudDBZone != null) {closeCloudDBZone();}AGConnectOptionsBuilder builder = new AGConnectOptionsBuilder().setRoutePolicy(SchoolDiaryApplication.getRegionRoutePolicy());AGConnectInstance instance = AGConnectInstance.buildInstance(builder.build(context));mCloudDB = AGConnectCloudDB.getInstance(instance, AGConnectAuth.getInstance());}/***在Application中初始化AGConnectCloudDB* @param context application context*/public static void initAGConnectCloudDB(Context context) {AGConnectCloudDB.initialize(context);}/***调用AGConnectCloudDB.createObjectType初始化schema*/public void createObjectType() {try {mCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo());} catch (AGConnectCloudDBException e) {Log.w(TAG, "createObjectType: " + e.getMessage());}}/*** 打开存储区*/public void openCloudDBZoneV2(DBZoneListener listener) {mConfig = new CloudDBZoneConfig(CLOUD_DB_NAME, CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);mConfig.setPersistenceEnabled(true);Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);openDBZoneTask.addOnSuccessListener(cloudDBZone -> {if (null != listener) {listener.getCloudDbZone(cloudDBZone);}mCloudDBZone = cloudDBZone;}).addOnFailureListener(e -> Log.w(TAG, "Open cloudDBZone failed for " + e.getMessage()));}/***调用AGConnectCloudDB.closeCloudDBZone接口*/public void closeCloudDBZone() {try {mCloudDB.closeCloudDBZone(mCloudDBZone);} catch (AGConnectCloudDBException e) {Log.w(TAG, "closeCloudDBZone: " + e.getMessage());}}/*** 添加更新任务列表的回调* @param uiTaskCallBack 更新任务列表的回调*/public void addTaskCallBacks(UiTaskCallBack uiTaskCallBack) {this.mUiTaskCallBack = uiTaskCallBack;}/*** 添加更新用户列表的回调*/public void addStudentCallBacks(UiStudentCallBack uiStudentCallBack) {this.mUiStudentCallBack = uiStudentCallBack;}/***查询TaskItems* @param query 查询条件*/public void queryTasks(CloudDBZoneQuery<TaskItem> query, int tag) {if (mCloudDBZone == null) {Log.w(TAG, "CloudDBZone is null, try re-open it");return;}Task<CloudDBZoneSnapshot<TaskItem>> queryTask = mCloudDBZone.executeQuery(query,CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);queryTask.addOnSuccessListener(snapshot -> processQueryResult(snapshot, tag)).addOnFailureListener(e -> mUiTaskCallBack.updateUiOnError("DB Query Error, Something went wrong!"));}/***查询UserData及状态* @param query 查询条件*/public void queryUserData(CloudDBZoneQuery<UserData> query, int tag) {if (mCloudDBZone == null) {Log.w(TAG, "CloudDBZone is null, try re-open it");return;}Task<CloudDBZoneSnapshot<UserData>> queryTask = mCloudDBZone.executeQuery(query,CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);queryTask.addOnSuccessListener(snapshot -> processUsersListResult(snapshot, tag)).addOnFailureListener(e -> mUiStudentCallBack.updateStudentUiOnError("DB Query Error, Something went wrong!"));}/***处理UserData和获取查询到的结果*/private void processUsersListResult(CloudDBZoneSnapshot<UserData> snapshot, int tag) {CloudDBZoneObjectList<UserData> taskItemCursor = snapshot.getSnapshotObjects();List<UserData> studentItemList = new ArrayList<>();try {while (taskItemCursor.hasNext()) {UserData studentItem = taskItemCursor.next();studentItemList.add(studentItem);}} catch (AGConnectCloudDBException e) {mUiTaskCallBack.updateUiOnError("DB Upsert Error, Something went wrong!");} finally {snapshot.release();}mUiStudentCallBack.onStudentAddOrQuery(studentItemList, tag);}/***处理TaskItem和获取查询到的结果*/private void processQueryResult(CloudDBZoneSnapshot<TaskItem> snapshot, int tag) {CloudDBZoneObjectList<TaskItem> taskItemCursor = snapshot.getSnapshotObjects();List<TaskItem> taskItemList = new ArrayList<>();try {while (taskItemCursor.hasNext()) {TaskItem taskItem = taskItemCursor.next();taskItemList.add(taskItem);}} catch (AGConnectCloudDBException e) {mUiTaskCallBack.updateUiOnError("DB Upsert Error, Something went wrong!");} finally {snapshot.release();}mUiTaskCallBack.onAddOrQuery(taskItemList, tag);}/***向上插入单个TaskItem* @param taskItem 本地添加或修改的TaskItem*/public void upsertTaskItem(TaskItem taskItem, int tag) {if (mCloudDBZone == null) {Log.w(TAG, "CloudDBZone is null, try re-open it");return;}Task<Integer> upsertTask = mCloudDBZone.executeUpsert(taskItem);upsertTask.addOnSuccessListener(cloudDBZoneResult -> {mUiTaskCallBack.onRefresh(tag);}).addOnFailureListener(e -> {mUiTaskCallBack.updateUiOnError("DB Upsert Error, Something went wrong!");});}/***向上插入大量TaskItem* @param taskItem 本地添加或修改的TaskItem*/public void upsertTaskItems(List<TaskItem> taskItem, int tag) {if (mCloudDBZone == null) {Log.w(TAG, "CloudDBZone is null, try re-open it");return;}Task<Integer> upsertTask = mCloudDBZone.executeUpsert(taskItem);upsertTask.addOnSuccessListener(cloudDBZoneResult -> {mUiTaskCallBack.onRefresh(tag);}).addOnFailureListener(e -> {mUiTaskCallBack.updateUiOnError("DB Upsert Error, Something went wrong!");e.printStackTrace();});}/***删除TaskItem* @param taskItemList 用户选择的任务*/public void deleteTaskItems(List<TaskItem> taskItemList) {if (mCloudDBZone == null) {Log.w(TAG, "CloudDBZone is null, try re-open it");return;}Task<Integer> deleteTask = mCloudDBZone.executeDelete(taskItemList);if (deleteTask.getException() != null) {mUiTaskCallBack.updateUiOnError("DB Deletion Error, Something went wrong!");return;}}}

4、该wrapper类包含一些在后续部分可复用的方法。

初始化wrapper,打开存储区,使用CloudDBZoneQuery接口检验用户是否是新用户。

/*** 初始化云数据库和数据库操作用于获取使用ID登录的用户*/private void initCloudDB() {mCloudDBZoneWrapper = new CloudDBZoneWrapper();mHandler = new Handler(Looper.getMainLooper());mHandler.post(() -> {if (null != AGConnectAuth.getInstance().getCurrentUser()) {mCloudDBZoneWrapper.createObjectType();mCloudDBZoneWrapper.openCloudDBZoneV2(mCloudDBZone -> {this.mCloudDBZone = mCloudDBZone;queryUserDetails();});}});}

5、存储区初始化成功后,在onInit中执行查询操作验证用户。

AGConnectUser user = AGConnectAuth.getInstance().getCurrentUser();new Handler().post(() -> {mCloudDBZoneWrapper.queryUserData(CloudDBZoneQuery.where(UserData.class).equalTo("UserID", user.getUid()), 1);});

6、在wrapper对象中添加监听器获取查询结果。已注册用户使用任意设备登录应用时,在本地的Shared Preference记录用户类型和匹配状态后,将用户引导至对应的界面。

mCloudDBZoneWrapper.addStudentCallBacks(new UiStudentCallBack() {@Overridepublic void onStudentAddOrQuery(List<UserData> studentItemList, int tag) {hideDialog();if (studentItemList.size() > 0) {UserData currentUser = studentItemList.get(0);int userType = Integer.parseInt(currentUser.getUserType());PrefUtil.getInstance(LoginActivity.this).setInt("USER_TYPE", userType);PrefUtil.getInstance(LoginActivity.this).setBool("IS_MAPPED", true);Intent i;if (userType == Constants.USER_STUDENT || userType == Constants.USER_TEACHER)i = new Intent(LoginActivity.this, HomeActivity.class);elsei = new Intent(LoginActivity.this, UserSelectionActivity.class);startActivity(i);finish();} else {Intent i = new Intent(LoginActivity.this, UserSelectionActivity.class);startActivity(i);finish();}}@Overridepublic void updateStudentUiOnError(String errorMessage) {hideDialog();showToast(errorMessage);}});

7、在页面的onDestroy方法中关闭存储区,否则可能导致错误发生。

若登录用户为新用户,引导至UserSelectionActivty获取用户类型。在选择用户类型后,将用户信息插入到云数据库中(在调用插入接口前需要初始化wrapper)。

/*** 向云数据库中插入用户类型*/public void insertUserType() {if (mCloudDBZone == null) {Log.e(TAG, "CloudDBZone is null, try re-open it");return;}showProgressDialog("Loading...");AGConnectUser user = AGConnectAuth.getInstance().getCurrentUser();UserData userData = new UserData();userData.setUserID(user.getUid());userData.setUserName(user.getDisplayName());userData.setUserType(String.valueOf(Constants.USER_TEACHER));Task<Integer> upsertTask = mCloudDBZone.executeUpsert(userData);upsertTask.addOnSuccessListener(cloudDBZoneResult -> {hideDialog();Toast.makeText(UserSelectionActivity.this, "TeacherMapActivity_user_insert_success " + cloudDBZoneResult + " records", Toast.LENGTH_SHORT).show();// 保存匹配状态和当前登录的用户类型。PrefUtil.getInstance(UserSelectionActivity.this).setInt("USER_TYPE", Constants.USER_TEACHER);PrefUtil.getInstance(UserSelectionActivity.this).setBool("IS_MAPPED", true);Intent i = new Intent(UserSelectionActivity.this, HomeActivity.class);startActivity(i);finish();});upsertTask.addOnFailureListener(e -> {hideDialog();Log.e(TAG, e.getMessage());Toast.makeText(UserSelectionActivity.this, "insert_failed " + e.getLocalizedMessage() + " records", Toast.LENGTH_SHORT).show();});}

用户信息成功插入后,若用户为学生则引导用户至StudentMapActivity。若用户为老师,直接引导至HomeActivity。

实现老师用户功能

老师的主页面包括两个Fragment。其一是TaskListFragment,其二是StudentListFragment。

TaskListFragment是老师创建的任务列表。

StudentListFragment是老师的学生列表。

1、制作创建作业的按钮UI。基于UI编写代码获取作业名称,作业描述以及提交作业的最晚日期。老师发布作业后,获取老师账户下匹配的学生列表,为每一位学生创建作业任务并将任务信息插入至云数据库。

/*** 收集学生列表,创建任务** 为所有学生创建任务。* @param data*/public void upsertTaskItem(Intent data) {mCloudDBZoneWrapper.addStudentCallBacks(new UiStudentCallBack() {@Overridepublic void onStudentAddOrQuery(List<UserData> studentItemList, int tag) {createAndInsertTaskList(data, studentItemList);}@Overridepublic void updateStudentUiOnError(String errorMessage) {Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();}});new Handler(Looper.getMainLooper()).post(() -> {mCloudDBZoneWrapper.queryUserData(CloudDBZoneQuery.where(UserData.class).equalTo("TeacherId", user.getUid()).and().equalTo("UserType", String.valueOf(Constants.USER_STUDENT)), 2);});}/***根据{@link CreateTaskActivity}获取的信息创建TaskItem的记录* 通过 wrapper类插入老师创建的TaskItem* 将创建一个具有共同ID的任务组* 和为每一个学生分发送一份具有唯一ID的作业任务。* @param taskData* @param studentsList*/private void createAndInsertTaskList(Intent taskData, List<UserData> studentsList) {List<TaskItem> taskItemList = new ArrayList();Date cDate = new Date();String taskGroupId = String.valueOf(UUID.randomUUID()); // unique for each TaskString date = "";for (int ind = 0; ind < studentsList.size(); ind++) {TaskItem task = new TaskItem();task.setTaskID(String.valueOf(UUID.randomUUID()));//unique for each studenttask.setGroup_id(taskGroupId);task.setTaskName(taskData.getStringExtra("task_name"));task.setTaskDescription(taskData.getStringExtra("task_desc"));task.setStatus(Constants.STATUS_NEW);task.setStudentID(studentsList.get(ind).getUserID());task.setCreadtedBy(user.getUid());date = taskData.getStringExtra("due_date");task.setDueDate(UserUtil.localToUTCDate(date));task.setCreatedDate(cDate);taskItemList.add(task);}mCloudDBZoneWrapper.upsertTaskItems(taskItemList, 0);}@Overridepublic void onRefresh(int tag) {generateTaskListQuery(index);}

2、插入数据后,向数据库查询作业列表,展示列表在TaskListFragment。每当进入该Fragment时和作业更新后都应当查询一次作业列表,这样主页就能一直展示最新信息。

/***创建查询今日TaskItem列表的Query* 根据登录用户为老师或学生展示不同的列表* 根据参数不同展示当前任务或历史任务* @param inputValue*/private void generateTaskListQuery(int inputValue) {binding.progressBar.setVisibility(View.VISIBLE);CloudDBZoneQuery<TaskItem> query;Date date = UserUtil.getCurrentDateTimeAsUTC();date.setHours(0);date.setMinutes(0);date.setSeconds(0);if (inputValue == Constants.TASK_ITEM) {query = CloudDBZoneQuery.where(TaskItem.class).greaterThanOrEqualTo("DueDate", date).and().notEqualTo("Status", STATUS_CLOSED);if (userType == Constants.USER_TEACHER)query = query.and().equalTo("CreadtedBy", user.getUid());if (userType == Constants.USER_STUDENT)query = query.and().equalTo("StudentID", user.getUid());getTaskListFromDB(query);} else if (inputValue == Constants.TASK_HISTORY_ITEM) {query = CloudDBZoneQuery.where(TaskItem.class).lessThanOrEqualTo("DueDate", date).and().equalTo("StudentID", user.getUid());getTaskListFromDB(query);} else {query = CloudDBZoneQuery.where(TaskItem.class);getTaskListFromDB(query);}}/***调用在wrapper类中定义的方法查询TaskItem* @param query*/private void getTaskListFromDB(CloudDBZoneQuery<TaskItem> query) {new Handler(Looper.getMainLooper()).postDelayed(() -> {mCloudDBZoneWrapper.queryTasks(query, 1);}, 500);}@Overridepublic void onAddOrQuery(List<TaskItem> taskItemList, int tag) {taskItemsList.clear();HashMap<String, TaskItem> tempMap = new HashMap<>();for (TaskItem taskItem : taskItemList) {if (!tempMap.containsKey(taskItem.getGroup_id())) {taskItemsList.add(taskItem);tempMap.put(taskItem.getGroup_id(), taskItem);}}taskAdapter.updateList(taskItemsList);binding.progressBar.setVisibility(View.GONE);}@Overridepublic void updateUiOnError(String errorMessage) {Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();}

3、老师点击任务时,打开TaskSummaryActivity页面。该页面展示作业信息、分配到作业任务的学生列表以及未提交和已提交的作业数量。老师们还可以在该页面上根据状态或需要关闭作业。

/*** 更新当前的任务状态为关闭*/private void closeCurrentTask() {if (taskItems.size() > 0) {List<TaskItem> closeTaskItems = new ArrayList<>();for (int i = 0; i < taskItems.size(); i++) {TaskItem taskItem = taskItems.get(i).getTaskItem();taskItem.setStatus(Constants.STATUS_CLOSED);closeTaskItems.add(taskItem);}new Handler(Looper.getMainLooper()).post(() -> {mCloudDBZoneWrapper.upsertTaskItems(closeTaskItems, 3);});}}/***从数据库中获取任务列表* @param groupId*/private void getSubmittedTaskList(String groupId) {new Handler(Looper.getMainLooper()).post(() -> {mCloudDBZoneWrapper.queryTasks(CloudDBZoneQuery.where(TaskItem.class).equalTo("group_id", groupId), 1);});}@Overridepublic void onAddOrQuery(List<TaskItem> taskItemList, int tag) {taskItems.clear();for (int ind = 0; ind < taskItemList.size(); ind++) {UserAndTask userAndTask = new UserAndTask();userAndTask.setTaskItem(taskItemList.get(ind));taskItems.add(userAndTask);}getStudentList();}/***从数据库获取学生列表*/private void getStudentList() {new Handler().post(() -> {mCloudDBZoneWrapper.queryUserData(CloudDBZoneQuery.where(UserData.class).equalTo("UserType", String.valueOf(Constants.USER_STUDENT)).and().equalTo("TeacherId", user.getUid()),2);});}@Overridepublic void onStudentAddOrQuery(List<UserData> studentItemList, int tag) {int pendingCount = 0, submittedCount = 0, evaluatedCount = 0;for (int ind = 0; ind < taskItems.size(); ind++) {//获取计数int tStatus = taskItems.get(ind).getTaskItem().getStatus();pendingCount += (tStatus == Constants.STATUS_NEW) ? 1 : 0;submittedCount += (tStatus == Constants.STATUS_SUBMITTED) ? 1 : 0;evaluatedCount += (tStatus == Constants.STATUS_EVALUATED) ? 1 : 0;//匹配任务和学生for (int jnd = 0; jnd < studentItemList.size(); jnd++) {if (taskItems.get(ind).getTaskItem().getStudentID().equals(studentItemList.get(jnd).getUserID())) {taskItems.get(ind).setUserData(studentItemList.get(jnd));}}}taskSumListAdapter.updateList(taskItems);displayTaskDetails(taskItems.get(0).getTaskItem(), pendingCount, submittedCount, evaluatedCount);hideDialog();}

4、学生上传的作业可以被老师批改。老师只需点击某位学生的作业即可。点击作业后打开TaskDetailActivtiy。该页面处理老师的作业批改和学生的作业提交。添加如下代码实现作业批改。

binding.btnValidateTask.setOnClickListener(v -> {if (validatePreValidation(taskItem)) {updateValidateStatus(taskItem);}});/*** 验证作业任务是否可以被批改*/private boolean validatePreValidation(TaskItem taskParam) {if (taskParam.getStatus() == Constants.STATUS_NEW) {showToast("Not submitted, Cannot evaluate");return false;} else if (taskParam.getStatus() == Constants.STATUS_EVALUATED) {showToast("Task already evaluated");return false;} else if (taskParam.getStatus() == Constants.STATUS_CLOSED) {showToast("Task Closed, Cannot evaluate");return false;} else if (taskParam.getAttachmentUrl() == null || taskParam.getAttachmentUrl().get().isEmpty()) {showToast("No Attachment, Cannot evaluate");return false;} else {return true;}}/***当老师批改作业时更新作业状态*更新老师批时的作业状态*/private void updateValidateStatus(TaskItem taskParam) {showProgressDialog("Updating Task status..");taskParam.setStatus(Constants.STATUS_EVALUATED);new Handler(Looper.getMainLooper()).post(() -> {mCloudDBZoneWrapper.upsertTaskItem(taskParam, VALIDATE);});}@Overridepublic void onRefresh(int tag) {hideDialog();if (tag == VALIDATE || tag == SUBMIT) {String msg = tag == VALIDATE ? "Task Validated." : "Task Submitted.";showAlertDialog(msg, () -> {HomeActivity.NEED_UPDATE = true;finish();});} else if (tag == ATTACHMENT) {showToast("File uploaded Successfully");}}

5、StudentsListFragment仅展示HomeActivity第二个页签的学生列表。执行数据库查询操作获取学生列表。

/*** 执行查询操作获取学生列表* @param inputValue* @param teacherId*/private void getStudentList(int inputValue, String teacherId) {binding.progressBar.setVisibility(View.VISIBLE);CloudDBZoneQuery<UserData> query;if (inputValue == Constants.STUDENT_ITEM) {query = CloudDBZoneQuery.where(UserData.class).equalTo("TeacherId", teacherId).and().equalTo("UserType", String.valueOf(Constants.USER_STUDENT));getListData(query);} else {query = CloudDBZoneQuery.where(UserData.class);getListData(query);}}/***调用wrapper类方法获取UserData* @param query*/private void getListData(CloudDBZoneQuery<UserData> query) {new Handler().postDelayed(() -> {mCloudDBZoneWrapper.queryUserData(query, 1);}, 300);}/**数据库监听器方法*OnResult方法实现检索学生列表* @param studentItemList* @param tag*/@Overridepublic void onStudentAddOrQuery(List<UserData> studentItemList, int tag) {studentAdapter.updateList(studentItemList);binding.progressBar.setVisibility(View.GONE);}/***数据库监听器方法*OnError方法实现检索学生列表* @param errorMessage*/@Overridepublic void updateStudentUiOnError(String errorMessage) {Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();}

6、若老师想查看学生历史作业任务,可以点击列表中的某位学生,然后打开StudentDetailActivity页面。在该页面中执行数据库查询操作获取列表。

/***添加UserData数据库操作监听器*通过wrapper类获取学生列表*/public void queryUserDetails() {mCloudDBZoneWrapper.addStudentCallBacks(new UiStudentCallBack() {@Overridepublic void onStudentAddOrQuery(List<UserData> studentItemList, int tag) {hideDialog();if (studentItemList.size() > 0) {UserData currentUser = studentItemList.get(0);binding.txtStudentName.setText(currentUser.getUserName());binding.txtStudentDetail.setText("My Student");getTaskList(currentUser.getUserID());}}@Overridepublic void updateStudentUiOnError(String errorMessage) {hideDialog();showToast(errorMessage);}});new Handler().post(() -> {mCloudDBZoneWrapper.queryUserData(CloudDBZoneQuery.where(UserData.class).equalTo("UserID", studentId), 1);});}/***创建用于获取特定学生数据的Query* @param studentId*/private void getTaskList(String studentId) {CloudDBZoneQuery<TaskItem> query;query = CloudDBZoneQuery.where(TaskItem.class).equalTo("StudentID", studentId);getListData(query);}/**** @param query*/private void getListData(CloudDBZoneQuery<TaskItem> query) {new Handler().post(() -> {mCloudDBZoneWrapper.queryTasks(query, 1);});}/***数据库监听器方法*OnResult方法实现检索TaskItem列表* @param taskItemList* @param tag*/@Overridepublic void onAddOrQuery(List<TaskItem> taskItemList, int tag) {hideDialog();taskListAdapter.updateList(taskItemList);}/***数据库监听器方法*onError实现检索TaskItem列表* @param errorMessage*/@Overridepublic void updateUiOnError(String errorMessage) {hideDialog();showToast(errorMessage);}

7、老师信息和二维码将展示在TeachersProfileActivity页面。添加下述代码生成老师的二维码。

/***初始化View,生成和展示带有老师信息的二维码*学生通过扫描该二维码可以匹配该老师* @param savedInstanceState*/@Overrideprotected void onPostCreate(@Nullable Bundle savedInstanceState) {super.onPostCreate(savedInstanceState);AGConnectUser user = AGConnectAuth.getInstance().getCurrentUser();String content = "{\"TeacherID\":\"" + user.getUid() + "\"," +"\"TeacherName\":\"" + user.getDisplayName() + "\"," +"\"EmailID\":\"" + user.getEmail() + "\"}";binding.txtTeacherName.setText(user.getDisplayName());binding.txtTeacherId.setText((user.getEmail() == null) ? "" : user.getEmail());int type = HmsScan.QRCODE_SCAN_TYPE;int width = 400;int height = 400;HmsBuildBitmapOption options = new HmsBuildBitmapOption.Creator().setBitmapMargin(3).create();try {//若HmsBuildBitmapOption对象未构建,将options设置为null。qrBitmap = ScanUtil.buildBitmap(content, type, width, height, options);((ImageView) findViewById(R.id.img_teacher_qr)).setImageBitmap(qrBitmap);} catch (WriterException e) {Log.w("buildBitmap", e);}}

实现学生用户功能

在StudentMapActivty页面,添加二维码扫描功能。通过统一扫码服务,学生可以扫码匹配老师。

初始化统一扫码服务扫描页面,实现onActivityResult方法。老师的二维码包含一串由统一扫码服务根据老师登录信息生成的JSON数据。

使用Intent中的结果数据在该Activity中执行下述数据库操作。

1、创建Loginmapping云数据库对象。

2、创建UserData云数据对象。

/***开启相机二维码扫描*/private void initScanQR() {HmsScanAnalyzerOptions options = new HmsScanAnalyzerOptions.Creator().setHmsScanTypes(HmsScan.QRCODE_SCAN_TYPE, HmsScan.DATAMATRIX_SCAN_TYPE).create();ScanUtil.startScan(StudentMapActivity.this, REQUEST_CODE, options);}/***二维码扫描后*传入老师二维码中的JSON对象* @param requestCode* @param resultCode* @param data*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode != RESULT_OK || data == null) {return;}if (requestCode == REQUEST_CODE) {//传入扫描的图片并返回结果。HmsScan obj = data.getParcelableExtra(ScanUtil.RESULT);if (obj != null && !TextUtils.isEmpty(((HmsScan) obj).getOriginalValue())) {try {/*二维码中的老师信息以JSON格式返回。*/JSONObject jsonObject = new JSONObject(obj.getOriginalValue());initCloudDB(jsonObject);} catch (JSONException e) {Log.e(TAG, e.getMessage());}} else {Log.e("Error", "Scanned result (null) not available");}} else {Log.e("Error", "Scanned result not available");}}/***初始化wrapper类。初始化完成后,调用数据库插入操作方法。* @param jsonObject*/private void initCloudDB(JSONObject jsonObject) {mCloudDBZoneWrapper = new CloudDBZoneWrapper();mHandler = new Handler(Looper.getMainLooper());mHandler.post(() -> {if (null != AGConnectAuth.getInstance().getCurrentUser()) {mCloudDBZoneWrapper.createObjectType();mCloudDBZoneWrapper.openCloudDBZoneV2(mCloudDBZone1 -> {this.mCloudDBZone = mCloudDBZone1;try {upsertTeacherDetails(jsonObject);} catch (JSONException e) {e.printStackTrace();}});}});}/***向云数据库中的LoginMapping插入老师和学生的匹配记录* @param jsonObject* @throws JSONException*/public void upsertTeacherDetails(JSONObject jsonObject) throws JSONException {if (mCloudDBZone == null) {Log.e(TAG, "CloudDBZone is null, try re-open it");return;}showProgressDialog("Loading...");AGConnectUser user = AGConnectAuth.getInstance().getCurrentUser();String teacherId = jsonObject.getString("TeacherID");Loginmapping loginmapping = new Loginmapping();loginmapping.setStudentID(user.getUid());loginmapping.setTeacherID(teacherId);loginmapping.setStudentName(user.getDisplayName());loginmapping.setStudentEmail(user.getEmail());loginmapping.setTeacherEmail(jsonObject.getString("EmailID"));loginmapping.setTeacherName(jsonObject.getString("TeacherName"));//loginmapping.setUserType(1);Date date = new Date();loginmapping.setMappedDate(date);Task<Integer> upsertTask = mCloudDBZone.executeUpsert(loginmapping);upsertTask.addOnSuccessListener(cloudDBZoneResult -> {insertUserType(teacherId);}).addOnFailureListener(e -> {hideDialog();Log.e("TAG", "insert_failed " + e.getLocalizedMessage() + " records");});}/***插入匹配后的学生记录* @param teacherId*/public void insertUserType(String teacherId) {if (mCloudDBZone == null) {Log.e(TAG, "CloudDBZone is null, try re-open it");return;}AGConnectUser user = AGConnectAuth.getInstance().getCurrentUser();UserData userData = new UserData();userData.setUserID(user.getUid());userData.setUserName(user.getDisplayName());userData.setUserType(String.valueOf(Constants.USER_STUDENT));userData.setTeacherId(teacherId);Task<Integer> upsertTask = mCloudDBZone.executeUpsert(userData);upsertTask.addOnSuccessListener(cloudDBZoneResult -> {hideDialog();Toast.makeText(StudentMapActivity.this, "Student Registered and Mapped.", Toast.LENGTH_SHORT).show();if (!initFrom.equals("StudentProfileActivity")) {PrefUtil.getInstance(this).setInt("USER_TYPE", Constants.USER_STUDENT);PrefUtil.getInstance(this).setBool("IS_MAPPED", true);startActivity(new Intent(StudentMapActivity.this, HomeActivity.class));}finish();});upsertTask.addOnFailureListener(e -> {hideDialog();Log.e(TAG, "insert_failed " + e.getLocalizedMessage() + " records");});}

3、匹配成功后,引导学生至HomeActivtiy。该页面包括两部分。复用TaskListFragment作为“Current Task”和“Task History”页签

Current Task页签列举需要完成的作业。

Task History列举所有历史作业。

/***创建获取今日TaskItem列表的Query* 基于登录用户,展示不同列表*根据参数不同展示当前TaskItem列表或历史任务* @param inputValue*/private void generateTaskListQuery(int inputValue) {binding.progressBar.setVisibility(View.VISIBLE);CloudDBZoneQuery<TaskItem> query;Date date = UserUtil.getCurrentDateTimeAsUTC();date.setHours(0);date.setMinutes(0);date.setSeconds(0);if (inputValue == Constants.TASK_ITEM) {query = CloudDBZoneQuery.where(TaskItem.class).greaterThanOrEqualTo("DueDate", date).and().notEqualTo("Status", STATUS_CLOSED);if (userType == Constants.USER_TEACHER)query = query.and().equalTo("CreadtedBy", user.getUid());if (userType == Constants.USER_STUDENT)query = query.and().equalTo("StudentID", user.getUid());getTaskListFromDB(query);} else if (inputValue == Constants.TASK_HISTORY_ITEM) {query = CloudDBZoneQuery.where(TaskItem.class).lessThanOrEqualTo("DueDate", date).and().equalTo("StudentID", user.getUid());getTaskListFromDB(query);} else {query = CloudDBZoneQuery.where(TaskItem.class);getTaskListFromDB(query);}}private void getTaskListFromDB(CloudDBZoneQuery<TaskItem> query) {new Handler(Looper.getMainLooper()).postDelayed(() -> {mCloudDBZoneWrapper.queryTasks(query, 1);}, 500);}@Overridepublic void onAddOrQuery(List<TaskItem> taskItemList, int tag) {taskItemsList.clear();HashMap<String, TaskItem> tempMap = new HashMap<>();for (TaskItem taskItem : taskItemList) {if (!tempMap.containsKey(taskItem.getGroup_id())) {taskItemsList.add(taskItem);tempMap.put(taskItem.getGroup_id(), taskItem);}}taskAdapter.updateList(taskItemsList);binding.progressBar.setVisibility(View.GONE);}

4、学生点击HomeActivity上对应的作业任务打开TaskDetailActivity页面后,上传作业图片。实现下述步骤。

1)从相册中挑选照片,上传照片至云存储,并获取上传照片的URL地址。

binding.btnUpload.setOnClickListener(view -> uploadFile(view));/***检查初始化后的云存储*调用图片挑选方法*/public void uploadFile(View view) {if (mAGCStorageManagement == null) {initAGCStorageManagement();}pickImageFromGallery();}/***初始化从设备相册挑选相册功能*/private void pickImageFromGallery() {Uri filePath = Uri.parse(Environment.getExternalStorageDirectory().getAbsolutePath());//Uri filePath = Uri.parse("/storage/");Intent intent = new Intent(Intent.ACTION_GET_CONTENT);intent.setDataAndType(filePath, "image/*");startActivityForResult(intent, PICKFILE_REQUEST_CODE);}/*** 获取图片的挑选结果并转成bitmap*将结果传入云存储的上传方法中*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == PICKFILE_REQUEST_CODE && resultCode == RESULT_OK && null != data) {imageUri = data.getData();ImageView imageView = new ImageView(TaskDetailActivity.this);imageView.setImageURI(imageUri);imageView.invalidate();BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable();uploadImageToCloud(drawable.getBitmap());}}/***为图片名称生成随机字串*开启图片上传异步方法*/private void uploadImageToCloud(Bitmap bitmap) {showProgressDialog("Uploading... ");final String randomKey = UUID.randomUUID().toString();FileFromBitmap fileFromBitmap = new FileFromBitmap(bitmap, randomKey, TaskDetailActivity.this);fileFromBitmap.execute();}/***图片上传AsyncTask类*doInBackground:通过相册路径获取文件*onPostExecute:上传至云存储*获取上传的文件URL用于展示或查看*/class FileFromBitmap extends AsyncTask<Void, Integer, File> {Context context;Bitmap bitmap;String fileName;public FileFromBitmap(Bitmap bitmap, String fileName, Context context) {this.bitmap = bitmap;this.context = context;this.fileName = fileName;}@Overrideprotected void onPreExecute() {super.onPreExecute();}@Overrideprotected File doInBackground(Void... voids) {File fileBackGround = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), fileName);ByteArrayOutputStream bos = new ByteArrayOutputStream();press(pressFormat.JPEG, 100, bos);byte[] bitmapdata = bos.toByteArray();FileOutputStream fos = null;try {fos = new FileOutputStream(fileBackGround);} catch (FileNotFoundException e) {e.printStackTrace();}try {fos.write(bitmapdata);fos.flush();fos.close();} catch (IOException e) {e.printStackTrace();}return fileBackGround;}@Overrideprotected void onPostExecute(File file) {if (!file.exists()) {return;}StorageReference storageReference = mAGCStorageManagement.getStorageReference(file.getPath());//成功则上传文件和获取URLUploadTask uploadTask = storageReference.putFile(file);Task<Uri> urlTask = uploadTask.continueWithTask(task -> {if (!task.isSuccessful()) {throw task.getException();}return storageReference.getDownloadUrl();});//上传完成后发送URLurlTask.addOnCompleteListener(task -> {if (task.isSuccessful()) {Uri downloadUri = task.getResult();System.out.println("Upload " + downloadUri);hideDialog();if (downloadUri != null) {String photoStringLink = downloadUri.toString(); //此处存储下载地址。addImageInRecyclerView(photoStringLink);}}});uploadTask.addOnSuccessListener(uploadResult -> hideDialog()).addOnFailureListener(e -> hideDialog());}}

2)串联所有上传的图片URL并更新相应任务的URL。

/***当学生上传附件时更新附件URL*需使用wrapper类的upsertTaskItem方法*/private void updateAttachURL(String uploadUrL) {showProgressDialog("Updating your attachments..");String str = taskItem.getAttachmentUrl() == null ? "" : taskItem.getAttachmentUrl().get();str += (str.isEmpty()) ? uploadUrL : ", " + uploadUrL;taskItem.setAttachmentUrl(new Text(str)); // 此处为附件URL字符串,多个地址用逗号隔开.new Handler(Looper.getMainLooper()).postDelayed(() -> {mCloudDBZoneWrapper.upsertTaskItem(taskItem, ATTACHMENT);}, 500);}@Overridepublic void onRefresh(int tag) {hideDialog();if (tag == VALIDATE || tag == SUBMIT) {String msg = tag == VALIDATE ? "Task Validated." : "Task Submitted.";showAlertDialog(msg, () -> {HomeActivity.NEED_UPDATE = true;finish();});} else if (tag == ATTACHMENT) {showToast("File uploaded Successfully");}}

3)任务中的URL更新后,接下来提交任务。提交完成后,任务状态变为“SUBMITTED”。

binding.btnSubmitTask.setOnClickListener(v -> {if (submitPreValidation(taskItem)) {updateSubmissionStatus(taskItem);}});/***验证任务数据是否能够被学生提交*/private boolean submitPreValidation(TaskItem taskParam) {if (taskParam.getStatus() == Constants.STATUS_SUBMITTED) {showToast("Task Already submitted");return false;} else if (taskParam.getStatus() == Constants.STATUS_EVALUATED) {showToast("Task evaluated, Cannot Submit");return false;} else if (taskParam.getStatus() == Constants.STATUS_CLOSED) {showToast("Task Closed, Cannot Submit");return false;} else if (taskParam.getAttachmentUrl() == null || taskParam.getAttachmentUrl().get().isEmpty()) {showToast("No Attachment, Cannot submit");return false;} else {return true;}}/***学生提交作业时更新任务状态*需使用wrapper类的upsertTaskItem方法*/private void updateSubmissionStatus(TaskItem task_Item) {showProgressDialog("Updating your attachments..");task_Item.setStatus(Constants.STATUS_SUBMITTED);new Handler(Looper.getMainLooper()).post(() -> {mCloudDBZoneWrapper.upsertTaskItem(task_Item, SUBMIT);Toast.makeText(this, "Url Updated successfully", Toast.LENGTH_SHORT).show();});}@Overridepublic void onRefresh(int tag) {hideDialog();if (tag == VALIDATE || tag == SUBMIT) {String msg = tag == VALIDATE ? "Task Validated." : "Task Submitted.";showAlertDialog(msg, () -> {HomeActivity.NEED_UPDATE = true;finish();});} else if (tag == ATTACHMENT) {showToast("File uploaded Successfully");}}

九、打包与测试

1、启用Androd Studio, 单击运行按钮在手机或模拟器上运行应用。点击登录按钮登录应用。

2、成功登录后,选择用户类型。

3、如果用户是学生,则需要扫码匹配老师。

4、老师的二维码在其个人主页下展示。

5、匹配成功后显示主页。

学生主页

老师主页

6、老师们点击主页“+”按钮创建任务。

7、学生和老师的主页都可以展示创建完成的任务。

8、学生可以在主页点击任务并更新任务状态。

9、老师可以查看任务状态和学生的历史任务。

十、恭喜您

祝贺您,您已成功构建一款School Diary应用并学会了:

在AppGallery Connect上配置云数据库和云存储。

使用Android Studio集成多个HMS Core服务并构建一款School Diary应用。

十一、参考

参考如下文档获取更多信息:

Auth Service

Cloud DB

Cloud Storage

Scan Kit

点击此处下载源码。

声明:本codelab实现多个HMS Core服务在单个项目中的集成,供您参考。您需要验证确保相关开源代码的安全合法合规。

欲了解更多更全技术文章,欢迎访问/consumer/cn/forum/?ha_source=zzh

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。