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

news2024/11/20 0:33:46

 一、介绍

总览

通过建立本次的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权限。

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

  • 云数据库

  • 云存储

  • 统一扫码服务

cke_53356.png

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

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

cke_36635.png

五、集成服务

Duration: 4:00

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

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

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

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

cke_50925.png

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

cke_59966.png

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

cke_67727.png

cke_70388.png

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

cke_156281.pngcke_159195.png

cke_164423.pngcke_167964.pngcke_186271.pngcke_180705.pngcke_261274.pngcke_206018.pngcke_210032.png

老师登录界面UI

cke_278723.pngcke_282548.pngcke_287492.png

cke_304939.pngcke_297907.pngcke_309734.png

cke_315202.png

七、前提准备

Duration: 5:00

认证服务

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

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

  2. 找到并点击项目。

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

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

cke_353127.png

云数据库

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

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

2、点击您的项目。

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

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

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

cke_369547.png

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

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

字段

类型

主键

非空

加密

默认值

TaskID

String

TaskName

String

TaskDescription

String

CreatedDate

Date

DueDate

Date

CreadtedBy

String

StudentID

String

Status

Integer

SubmittedDate

Date

group_id

String

AttachmentUrl

Text

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

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

cke_455576.png

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

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

Loginmapping

字段

类型

主键

非空

加密

默认值

StudentID

String

StudentName

String

StudentEmail

String

TeacherID

String

TeacherName

String

TeacherEmail

String

UserType

Integer

MappedDate

Date

UserData

字段

类型

主键

非空

加密

默认值

UserID

String

UserType

String

UserName

String

TeacherId

String

12、单击“导出”。

cke_565871.png

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

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

cke_642919.png

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

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

cke_693846.png

云存储

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

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

cke_729598.png

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

cke_769420.png

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。

@Override
protected 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() {
     @Override
     public 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);
             else
                 i = new Intent(LoginActivity.this, UserSelectionActivity.class);
             startActivity(i);
             finish();
         } else {
             Intent i = new Intent(LoginActivity.this, UserSelectionActivity.class);
             startActivity(i);
             finish();
         }
     }

     @Override
     public 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() {
         @Override
         public void onStudentAddOrQuery(List<UserData> studentItemList, int tag) {
             createAndInsertTaskList(data, studentItemList);
         }

         @Override
         public 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 Task
     String date = "";
     for (int ind = 0; ind < studentsList.size(); ind++) {
         TaskItem task = new TaskItem();
         task.setTaskID(String.valueOf(UUID.randomUUID()));//unique for each student
         task.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);
 }
@Override
public 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);
 }
@Override
public 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);
 }
@Override
public 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);
     });
 }

@Override
public 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);
     });
 }
@Override
public 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);
     });
 }
@Override
public 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
  */
@Override
public void onStudentAddOrQuery(List<UserData> studentItemList, int tag) {
     studentAdapter.updateList(studentItemList);
     binding.progressBar.setVisibility(View.GONE);
}

/**
  *数据库监听器方法
  *OnError方法实现检索学生列表
  * @param errorMessage
  */
@Override
public void updateStudentUiOnError(String errorMessage) {
     Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();
 }

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

/**
  *添加UserData数据库操作监听器
  *通过wrapper类获取学生列表
  */
public void queryUserDetails() {
     mCloudDBZoneWrapper.addStudentCallBacks(new UiStudentCallBack() {
         @Override
         public 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());
             }
         }

         @Override
         public 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
  */
@Override
public void onAddOrQuery(List<TaskItem> taskItemList, int tag) {
     hideDialog();
     taskListAdapter.updateList(taskItemList);
}

/**
  *数据库监听器方法
  *onError实现检索TaskItem列表
  * @param errorMessage
  */
@Override
public void updateUiOnError(String errorMessage) {
     hideDialog();
     showToast(errorMessage);
 }

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

/**
  *初始化View,生成和展示带有老师信息的二维码
  *学生通过扫描该二维码可以匹配该老师
  * @param savedInstanceState
  */
@Override
protected 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
  */
@Override
protected 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);
 }
@Override
public 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
  *将结果传入云存储的上传方法中
  */
@Override
protected 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;
     }

     @Override
     protected void onPreExecute() {
         super.onPreExecute();
     }

     @Override
     protected File doInBackground(Void... voids) {
         File fileBackGround = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), fileName);
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         bitmap.compress(Bitmap.CompressFormat.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;
     }

     @Override
     protected void onPostExecute(File file) {
         if (!file.exists()) {
             return;
         }
         StorageReference storageReference = mAGCStorageManagement.getStorageReference(file.getPath());
         //成功则上传文件和获取URL
         UploadTask uploadTask = storageReference.putFile(file);
         Task<Uri> urlTask = uploadTask.continueWithTask(task -> {
             if (!task.isSuccessful()) {
                 throw task.getException();
             }
             return storageReference.getDownloadUrl();
         });
         //上传完成后发送URL
         urlTask.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);
 }
@Override
public 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();
     });
 }
 
@Override
public 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, 单击运行按钮在手机或模拟器上运行应用。点击登录按钮登录应用。

cke_2160757.pngcke_2191947.png

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

cke_2218850.png

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

cke_2288975.png

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

cke_2368717.png

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

  • 学生主页

cke_2537726.png

  • 老师主页

cke_2637171.png

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

cke_2732057.pngcke_2798355.png

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

cke_2880527.png

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

cke_2975191.pngcke_3042525.png

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

cke_3137009.pngcke_3170147.png

十、恭喜您

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

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

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

十一、参考

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

  • Auth Service

  • Cloud DB

  • Cloud Storage

  • Scan Kit

点击此处下载源码。

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

 欲了解更多更全技术文章,欢迎访问https://developer.huawei.com/consumer/cn/forum/?ha_source=zzh

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/29300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud微服务(九)——Ribbon负载均衡

Ribbon负载均衡服务调用 SpringCloud 已停更 github官网&#xff1a;https://github.com/netflix/ribbon Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具&#xff0c;它基于Netflix Ribbon实现。通过Spring Cloud的封装&#xff0c;可以让我们轻松地将面向服务…

高斯分布的乘积与卷积

高斯分布是一个很重要的连续分布形式&#xff0c;频繁出现各种应用场景里也可以导出很多分布&#xff0c;如在典型的线性回归中对误差 的建模就是用的标准正态分布&#xff0c;统计学的学生分布就是从正态分布中导出。随着贝叶斯统计学的广泛应用&#xff0c;相乘的高斯分布&am…

【仿真建模】第四课:AnyLogic入门基础课程 - 轨道交通仿真入门讲解

文章目录一、轨道库的概念和特点二、轨道交通仿真三、更换车头和车身样式一、轨道库的概念和特点 二、轨道交通仿真 新建模型 搭建轨道 定义轨道上的起点和终点 拖拽出一个trainSource&#xff0c;设置其车厢数量为4&#xff08;默认为11&#xff0c;车厢太多会超出轨道&…

nginx(六十)proxy模块(一)proxy_pass指令

一 proxy模块处理请求的流程 ① 流程图 说明&#xff1a; nginx从client接收的是http协议,转发给上游的也是http协议备注&#xff1a; 后续根据处理请求的流程,来讲解相关指令 二 proxy_pass ① 基本解读 说明&#xff1a; proxy_pass是一个动作指令 ② proxy_pass的…

【题型总结】找到第n个自定义数 | 丑数系列 + 神奇数字

文章目录找到第n个自定义数丑数【LC263】丑数Ⅱ【LC264】优先队列多指针超级丑数【LC313】优先队列【超时】多指针第N个神奇数字【LC878】找规律二分查找数学丑数Ⅲ【LC1201】二分查找数学总结找到第n个自定义数 因为神奇数字做了相关的题目&#xff0c;个人建议做题顺序&…

涨知识!Python 的异常信息还能这样展现

【导语】&#xff1a;在日常开发的过程中&#xff0c;当代码报错时&#xff0c;我们通常要不断打印、阅读traceback提示信息&#xff0c;来调试代码&#xff0c;这篇文章介绍了如何实现一个Exception Hooks&#xff0c;使得traceback模块的提示信息更加精确&#xff1b;同时还介…

java项目-第159期ssm超市管理系统_ssm毕业设计_计算机毕业设计

java项目-第159期ssm超市管理系统-ssm毕业设计_计算机毕业设计 【源码请到资源专栏下载】 今天分享的项目是《ssm超市管理系统》 该项目分为2个角色&#xff0c;管理员、员工。 员工登录后台主要负责商品的出入库&#xff0c;以及个人事项办理&#xff0c;比如&#xff1a; 上…

技术分享| 快对讲视频调度功能说明

随着计算机技术的日趋成熟&#xff0c;融合调度方案已经在行业信息化中普及&#xff0c;由于近几年实时音视频能力的提升&#xff0c;融合调度中的视频调度方案也在往实时性、高清方向靠拢。快对讲视频调度正是结合了视频监控&#xff0c;以及实时通信的特性&#xff0c;在市面…

传奇GOM引擎单机架设图文教程

T:准备下载好服务端&#xff08;版本&#xff09;gom引擎架设 选择GOM引擎版本 注;版本可以去论坛有免费&#xff0c;电脑还需要下载安装好客户端。 1.首先下载好版本后会有2个压缩包&#xff0c;一个是版本&#xff0c;一个是补丁&#xff0c; 简单来说架设分三部&#xff1…

Scrum 四个会议的正确召开方式

敏捷开发有一些重要的实践方法&#xff0c;可以帮助团队更快地适应敏捷开发框架。这些方法不能简单照搬执行&#xff0c;比如&#xff0c;只在瀑布开发模式下中加入 Scrum 的四个会议&#xff0c;这无法让瀑布团队转成敏捷团队。敏捷转型需要深入理解概念和思维&#xff0c;团队…

【Linux】第十四章 多线程(生产者消费者模型+POSIX信号量)

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

JavaSE中split方法详细原理讲解分析

文章目录方法1:split(String [regex](https://so.csdn.net/so/search?qregex&spm1001.2101.3001.7020))入门案例1入门案例2入门案例3入门案例4分隔符三个特殊位置特殊案例1特殊案例2方法2:split(String regex,int limit)limit用法:进阶案例:limit>0限制分割次数limit&l…

图神经网络关系抽取论文阅读笔记(二)

1 用于关系抽取的生成式参数图神经网络 论文&#xff1a;Graph Neural Networks with Generated Parameters for Relation Extraction&#xff08;2019 ACL&#xff09; 1.1 创新点 传统的图神经网络在进行NLP任务时&#xff0c;图的拓扑结构都是预先定义好的&#xff0c;之后再…

已解决OSError: [Errno 22] Invalid argument

已解决OSError: [Errno 22] Invalid argument 文章目录报错代码报错翻译报错原因解决方法帮忙解决报错代码 粉丝群里面的一个粉丝用Python读取文件的时候&#xff0c;发生了报错&#xff08;跑来找我求助&#xff0c;然后顺利帮助他解决了&#xff0c;顺便记录一下希望可以帮助…

PTA题目 前世档案

网络世界中时常会遇到这类滑稽的算命小程序&#xff0c;实现原理很简单&#xff0c;随便设计几个问题&#xff0c;根据玩家对每个问题的回答选择一条判断树中的路径&#xff08;如下图所示&#xff09;&#xff0c;结论就是路径终点对应的那个结点。 现在我们把结论从左到右顺序…

UnityVR一体机报错:GL_OUT_OF_MEMORY,[EGL] Unable to acquire context

一、 报错信息一览 &#xff08;1&#xff09; [EGL] Unable to acquire context: E Unity : [EGL] Unable to acquire context: EGL_BAD_SURFACE: An EGLSurface argument does not name a valid surface (window, pixel buffer or pixmap) configured for GL rendering. 解决…

AQS源码解析 4.Condition条件队列入门_手写BrokingQueue

AQS源码解析—Condition条件队列入门_手写BrokingQueue Condition 条件队列介绍 AQS 中还有另一个非常重要的内部类 ConditionObject&#xff0c;它实现了 Condition 接口&#xff0c;主要用于实现条件锁。ConditionObject 中也维护了一个队列&#xff0c;这个队列主要用于等…

动态规划算法学习三:0-1背包问题

文章目录前言一、问题描述二、DP解决步骤1、最优子结构性质2、状态表示和递推方程3、算法设计与分析4、计算最优值5、算法实现6、缺点与思考前言 一、问题描述 二、DP解决步骤 1、最优子结构性质 2、状态表示和递推方程 子问题可由两个参数确定&#xff1a;待考虑装包的物品…

【C/调试实用技巧】—作为程序员应如何面对并尝试解决Bug?

小菜坤日常上传gitee代码&#xff1a;https://gitee.com/qi-dunyan 关于C语言的所有由浅入深的知识&#xff0c;都存放在专栏【c】 ❤❤❤ 个人简介&#xff1a;双一流非科班的一名小白&#xff0c;期待与各位大佬一起努力&#xff01; 推荐网站&#xff1a;cplusplus.com 目录…

Springboot集成ItextPdf

目录 一、概述 二、Itext API 1.PDF文档生成 3.常用对象 一、文档对象 二、操作对象 三、内容对象 四、表格对象 四、常用案例 一、水印 三、页眉页脚 四、合并多个PDF 五、表单PDF 六、模板PDF 一、html模板 二、使用工具构建PDF模板 7、HTML转PDF 8、删除页…